7 个版本
0.3.3 | 2024 年 2 月 16 日 |
---|---|
0.3.2 | 2023 年 12 月 2 日 |
0.3.0 | 2023 年 11 月 11 日 |
0.2.1 | 2023 年 11 月 6 日 |
0.1.0 | 2023 年 11 月 4 日 |
#217 在 文件系统
每月 22 次下载
用于 norad
380KB
645 行
close_already
- 加速在 Windows 上写入大量文件的程序
在 Windows 上关闭文件较慢,耗时 1-10 毫秒,而 MacOS、Linux 等系统仅需微秒级。 “为什么?” 在 Gregory Szorc 的博客文章 这篇文章 中解释了,并建议使用线程池来处理 Windows 上文件句柄的关闭。这正是这个 crate 实现的功能,同时尽可能地不对开发者造成干扰。即使不使用此 crate,在 rustup 和 Mercurial 中也有案例研究,显示这种技术显著提高了性能
我应该使用它吗?
如果你正在写入相对较小的文件,数量级在数百或更多,那么你很可能将从 close_already
中受益。它旨在易于切换和使用,所以试一试,并进行基准测试!请注意,如果你的代码已经尝试使用多个线程/核心来处理文件(例如使用 rayon
),性能提升将会更加有限
兼容性
每个列出的后端都带有相应的功能 backend-<name>
。要使用非默认后端,请设置 default-features = false
并启用相应的 backend-<name>
功能
支持的后端
threadpool
- 默认,创建并使用自己的操作系统线程线程池blocking
- 使用blocking
的线程池rayon
- 使用rayon
的全局线程池async-std
- 使用async-std
的全局执行器。支持async_std
的File
smol
- 使用smol
的全局执行器。支持smol
的File
tokio
- 使用tokio
的全局执行器。支持tokio
的File
。启用rt
和fs
功能
如何使用它?
使用默认的 threadpool
后端将其添加到您的项目中
cargo add close_already
或使用不同的后端(有关可用后端,请参阅 兼容性)
cargo add close_already -F backend-<name> --no-default-features
您可以使用 FastClose
构造函数(FastClose::new
)构建一个 FastClose
,或者利用 FastCloseable
特性并调用 .fast_close()
来包装您的类型。支持标准库中的 File
类型以及提供替代方案的任何后端。就是这样。
或者如果您是 std::fs::read
和 std::fs::write
用户,那么所有可以利用 close_already
的函数都已在 fs
模块中重新实现
如果我并不总是针对/开发在 Windows 上怎么办?
没问题!FastClose
不会创建/使用线程池,也不会将文件关闭操作发送到线程池,但所有相同的结构体/方法/特性都将可用,因此您不需要在所有地方进行条件编译 #[cfg]
close_already
是如何工作的?
如前所述,基本原理是提供一个处理文件关闭操作的线程池
此实现使用一个零大小的包装类型 FastClose
(无内存开销,哇!),它有一个自定义的 Drop
实现将文件句柄发送到线程池,以便在不再需要时允许多个线程并行等待文件关闭。当第一个 FastClose
被丢弃时(使用新稳定的 OnceLock
),线程池会被惰性初始化
FastClose
结构体实现了 Deref
和 DerefMut
,这意味着在所有意图和目的上,您可以完全忽略它的存在,然后让魔法在它超出作用域时发生
最佳之处在于解决方案的实现非常简洁,基本核心逻辑不超过30行;大部分的复杂度来自于委托特性行为和提供标准库便利函数的等价实现
(*在非threadpool
后端,使用全局线程池/执行器)
它工作吗?
合成基准测试
以下是我在机器(Ryzen 5600,Sabrent Rocket 4 NVMe SSD)上进行的纯写入性能时间,与异步后端进行对比。基准测试涉及从Roboto Regular UFO中写入约2300个.glif文件
Writing/std::fs/Roboto-Regular.ufo
time: [1.4257 s 1.4484 s 1.4712 s]
Writing/close_already blocking/Roboto-Regular.ufo
time: [1.3094 s 1.3155 s 1.3223 s]
Writing/close_already rayon/Roboto-Regular.ufo
time: [1.2031 s 1.2134 s 1.2241 s]
Writing/close_already threadpool/Roboto-Regular.ufo
time: [1.2057 s 1.2143 s 1.2241 s]
总结来说,您可以观察到写入时间有9-16%的有效降低,当然这当然会根据工作负载而有所不同
案例研究:norad
norad
是一个支持统一字体对象标准的库,这是一种以拥有大量文件而臭名昭著的源文件格式。例如,查看Roboto Regular,这是以下基准测试中使用的示例'合理大小'字体
比较带close_already
和不带close_already
的单线程norad
norad (default):
write Roboto-Regular.ufo
time: [2.0756 s 2.0973 s 2.1211 s]
norad (default) + close_already (threadpool):
write Roboto-Regular.ufo
time: [975.15 ms 1.0152 s 1.0596 s]
速度快了两倍!
关于已经多线程的工作负载?norad
有可选择的rayon
支持
norad (rayon):
write Roboto-Regular.ufo
time: [867.16 ms 922.49 ms 985.35 ms]
norad (rayon) + close_already (rayon):
write Roboto-Regular.ufo
time: [831.17 ms 871.48 ms 915.87 ms]
尽管norad
已经从使用rayon
中获得了2倍的速度提升,但仍然快10%以上!
您可以在我的分支上运行数字,使用cargo bench
和before
/after
标签
从v0.14版本开始,close_already
在norad
中用于所有工作负载
贡献
有一个Justfile,方便在多个后端之间运行检查和测试。它需要安装cargo-hack
,并且需要为您的工具链配置x86_64-pc-windows-msvc
目标。运行just
以查看可用的食谱
请确保在提交PR时,您的代码使用nightly rustfmt
格式化,并且任何后端都没有Clippy lint
我想添加对_____后端的支持!
去做吧!将其放在特性门后面,将特性名称添加到mutually_exclusive_features::exactly_one_of!
块中,该块位于lib.rs
顶部,然后为windows::FastClose
添加一个新定义,该定义由您的特性标志启用。如果您正在懒加载自己的线程池/执行器,那么您自然也需要一个static OnceLock
,就像backend-threadpool
一样工作。就这样!
对于提供自己文件类型的异步后端,您可能还希望在该类型上实现FastCloseable
,并转发任何相关的特性行为(例如,Async{Read,Seek,Write}
)。请参阅mod smol_impls
以获取示例
我想添加对_____特性行为的支持!
大胆去做!确保泛型界限包括 H: Send + 'static
,它应该会顺利工作。如果你添加支持的特质不是标准库的一部分(或是在nightly版本中),请将其放在功能门后面(默认关闭)。
许可证
MIT或Apache 2,任选其一(与Rust本身相同)
依赖项
~0–11MB
~121K SLoC