3 个版本
0.1.3 | 2024 年 6 月 10 日 |
---|---|
0.1.2 | 2024 年 5 月 6 日 |
0.1.1 | 2024 年 5 月 6 日 |
305 在 文件系统
153 每月下载量
27KB
379 行
Win Tree
一个递归方式获取指定目录树结构信息的程序。与 Windows tree
命令 相同,因此命名为 win_tree
,但这个支持 JSON 格式化、深度控制和基于名称匹配的文件过滤。
用例
此命令适用于需要完整备份数据但成本过高的场景,因此可以拍摄文件结构树的快照并进行备份,以便至少可以获得文件名,并且可以在需要时再次获取这些文件。(我正在使用此命令每天对我的 8 TB 的外置硬盘进行快照,该硬盘包含所有我的媒体。)
参数
- [必选] 路径 - 必须始终是第一个参数。
- [可选] 深度 - [-d ] 控制生成树要深入的程度。注意,如果目录的子目录由于深度控制而没有包含在树中,则这些目录的
size_in_bytes
以及所有父目录的级联将都是 null,因为没有评估子目录就报告它们是不正确的。 - [可选] 排除 - [-e <regex_pattern>] 控制要排除的路径。
- [可选] 构建方法 - [-m <method_name>] 控制用于构建树的方法。以下是一些选项 -
- serial-async - 无并行化,递归实现。
- par-rayon - 使用 rayon 的
par_bridge
在read_dir
迭代器上进行并行化,递归实现。 - par-tp - 使用自定义编写的线程池进行并行化,纯函数实现。
如何使用
# [Install cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html)
- `cargo install win_tree`
- `win_tree <path> -d <depth> -e "<pattern>" > snapshot.json`
本地结果
单线程构建和构建-async
在我的外置硬盘上运行的结果,容量为 8 TB,具有约 4.2 TB 的数据,分布在约 15600 个文件中 - 在 74.27 秒内构建了快照。
saurabh@Saurabh-Raider:/mnt/d/Saurabh/Personal/rust_labs$ cargo run -p win_tree -- /mnt/f/stuff/ -e "^(?:\..*|doc|debug)" -m serial-async > snapshot.json
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.36s
Running `target/debug/win_tree /mnt/f/stuff/ -e '^(?:\..*|doc|debug)'`
Built in **74.273552703s**
Serialised in **134.912809ms**
多线程构建-par 使用 rayon
在我的外置硬盘上运行的结果,容量为 8 TB,具有约 4.2 TB 的数据,分布在约 15600 个文件中 - 在 17.88 秒内构建了快照。
saurabh@Saurabh-Raider:/mnt/d/Saurabh/Personal/rust_labs$ cargo run -p win_tree -- /mnt/f/stuff/ -e "^(?:\..*|doc|debug)" -m par-rayon > snapshot.json
warning: virtual workspace defaulting to `resolver = "1"` despite one or more workspace members being on edition 2021 which implies `resolver = "2"`
note: to keep the current resolver, specify `workspace.resolver = "1"` in the workspace root's manifest
note: to use the edition 2021 resolver, specify `workspace.resolver = "2"` in the workspace root's manifest
note: for more details see https://doc.rust-lang.org/cargo/reference/resolver.html#resolver-versions
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.36s
Running `target/debug/win_tree /mnt/f/stuff/ -e '^(?:\..*|doc|debug)'`
Built in 17.884196059s
Serialised in 149.379899ms
多线程构建-par 使用自定义编写的线程池
这相当复杂才能实现,并且没有产生更好的结果。完成这个的原因纯粹是为了学习如何实现。以下是一些关于此逻辑的思考步骤 -
- 纯函数 -
- 在threadpool中运行的作业必须是纯函数。
- 这需要修改针对单一路径工作的单元函数。
- 共享内存被用于配置选项以及父指针。
- 配置为只读,因此使用了可复制的数据,并且父对象可写(用于链接子对象和更新大小),因此它被放置在Arc
>后面。
- 作业队列 -
- 将作业添加到线程池进行递归操作并不直观。感谢这个stackoverflow问题的答案,我意识到我们应该将新作业入队的逻辑放在作业本身中。
- 内存管理、引用计数和生成结果 -
- 简单来说,我们正在创建这些TreeNode,它们同时引用其父节点和子节点,并且可以在任何时候进行更新。所有这些操作都是由n个线程池中的线程并发执行的。
- 如果我们只是将所有共享引用保持在Arc
>后面并在需要时更新它们,这一切都很简单。棘手的部分是在所有作业完成后,从锁和解包器中取出实际节点,以便将结果共享给调用者。 - 一个选项是等待所有作业完成,然后遍历整个包装对象的树并创建一个包含内部对象的克隆。这种方法有两个问题 - 克隆时的额外内存使用和遍历序列执行所花费的额外时间。
- 相反,我使用了一种依赖于Arc设计基础的方法,即引用计数。对于每个节点处理,我使用
Arc::try_unwrap()
函数来决定节点是否没有更多子节点,这将是在所有子节点都完成了其处理并放弃了对其父节点的引用时的情况。有了这个,我取出了Arc和Mutex包装器外的节点,并将其发送到结果通道。 - 一篇好的阅读材料 - https://doc.rust-lang.net.cn/book/ch15-06-reference-cycles.html。
在外部硬盘上运行的结果,该硬盘容量为8 TB,数据大小为约4.2 TB,跨越约15600个文件 - 在< ? >秒内构建快照。
<Pending>
依赖关系
~4.5–7MB
~130K SLoC