#file #fs-file #windows-file #handle #fs #blog-post #windows

close_already

加速在 Windows 上写入大量文件的程序

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

MIT/Apache

380KB
645

close_already - 加速在 Windows 上写入大量文件的程序

GitHub Actions Crates.io Dependencies

在 Windows 上关闭文件较慢,耗时 1-10 毫秒,而 MacOS、Linux 等系统仅需微秒级。 “为什么?” 在 Gregory Szorc 的博客文章 这篇文章 中解释了,并建议使用线程池来处理 Windows 上文件句柄的关闭。这正是这个 crate 实现的功能,同时尽可能地不对开发者造成干扰。即使不使用此 crate,在 rustupMercurial 中也有案例研究,显示这种技术显著提高了性能

我应该使用它吗?

如果你正在写入相对较小的文件,数量级在数百或更多,那么你很可能将从 close_already 中受益。它旨在易于切换和使用,所以试一试,并进行基准测试!请注意,如果你的代码已经尝试使用多个线程/核心来处理文件(例如使用 rayon),性能提升将会更加有限

兼容性

每个列出的后端都带有相应的功能 backend-<name>。要使用非默认后端,请设置 default-features = false 并启用相应的 backend-<name> 功能

支持的后端

  • threadpool - 默认,创建并使用自己的操作系统线程线程池
  • blocking - 使用 blocking 的线程池
  • rayon - 使用 rayon 的全局线程池
  • async-std - 使用 async-std 的全局执行器。支持 async_stdFile
  • smol - 使用 smol 的全局执行器。支持 smolFile
  • tokio - 使用 tokio 的全局执行器。支持 tokioFile。启用 rtfs 功能

如何使用它?

使用默认的 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::readstd::fs::write 用户,那么所有可以利用 close_already 的函数都已在 fs 模块中重新实现

如果我并不总是针对/开发在 Windows 上怎么办?

没问题!FastClose 不会创建/使用线程池,也不会将文件关闭操作发送到线程池,但所有相同的结构体/方法/特性都将可用,因此您不需要在所有地方进行条件编译 #[cfg]

close_already 是如何工作的?

如前所述,基本原理是提供一个处理文件关闭操作的线程池

此实现使用一个零大小的包装类型 FastClose(无内存开销,哇!),它有一个自定义的 Drop 实现将文件句柄发送到线程池,以便在不再需要时允许多个线程并行等待文件关闭。当第一个 FastClose 被丢弃时(使用新稳定的 OnceLock),线程池会被惰性初始化

FastClose 结构体实现了 DerefDerefMut,这意味着在所有意图和目的上,您可以完全忽略它的存在,然后让魔法在它超出作用域时发生

最佳之处在于解决方案的实现非常简洁,基本核心逻辑不超过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 benchbefore/after标签

从v0.14版本开始,close_alreadynorad中用于所有工作负载

贡献

有一个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