6 个版本 (3 个重大变更)
0.4.1 | 2023 年 8 月 5 日 |
---|---|
0.4.0 | 2023 年 8 月 5 日 |
0.3.1 | 2023 年 7 月 28 日 |
0.2.0 | 2023 年 7 月 26 日 |
0.1.0 | 2023 年 7 月 25 日 |
#537 in 命令行工具
21KB
161 行
ratiscat
rat
是 Rust 中高性能的 cat
重新实现
结果证明,老鼠比猫更适合管道。
请参阅 rat.rs
也可以查看 uutils
项目
https://github.com/uutils/coreutils/
用法
在以下环境中运行并比较:i7-6800K / 128GB DDR4 (文件读取缓存) / Linux 6.3.9 / btrfs (CoW) / coreutils 9.3 / uutils 0.0.20
基本功能(原始 I/O)
# 4GB random data sample
$ dd if=/dev/urandom of=test.rand bs=1MB count=4096
# file to file
$ time rat test.rand >test.$(date +%s)
real 0m1.715s # <-- io::copy uses sendfile() first then copy_file_range() :(
user 0m0.001s
sys 0m1.710s
$ time cat test.rand >test.$(date +%s)
real 0m0.004s # <-- cat uses copy_file_range() all the time, great for btrfs
user 0m0.004s
sys 0m0.000s
# file to pipe
$ rat test.rand | pv -r >/dev/null
[2.69GiB/s] # <-- rat automagically configures size for FIFO pipes
$ cat test.rand | pv -r >/dev/null
[2.02GiB/s] # <-- cat does 128K writes onto default 64K sized FIFO pipe (???)
# from char devices
$ timeout 5 rat </dev/zero | pv -ab >/dev/null
25.2GiB [5.05GiB/s]
$ timeout 5 cat </dev/zero | pv -ab >/dev/null
16.0GiB [4.00GiB/s]
# between pipes
$ timeout -s SIGINT 5 yes | rat | pv -r >/dev/null
[4.68GiB/s]
$ timeout -s SIGINT 5 yes | cat | pv -r >/dev/null
[2.80GiB/s]
参数排序、错误日志、健全性检查
$ echo test | rat - /does/not/exists /etc/hosts /does/not/exists2 | md5sum
rat: /does/not/exists: No such file or directory
rat: /does/not/exists2: No such file or directory
27f2e6689a97a42813e55d44ef29cda4 -
$ rat < foo >> foo
rat: -: input file is output file
$ echo test | cat - /does/not/exists /etc/hosts /does/not/exists2 | md5sum
cat: /does/not/exists: No such file or directory
cat: /does/not/exists2: No such file or directory
27f2e6689a97a42813e55d44ef29cda4 -
$ cat < foo >> foo
cat: -: input file is output file
与 pv
和 uu-cat
的一些比较
$ timeout 5 pv -r </dev/zero >/dev/null
[20.3GiB/s]
$ timeout 5 yes | pv -r >/dev/null
[6.13GiB/s]
$ timeout 5 pv -r </dev/zero | pv -q >/dev/null
[3.39GiB/s]
$ timeout 5 pv -r </dev/zero | uu-cat >/dev/null
[3.66GiB/s]
$ timeout 5 pv -r </dev/zero | rat >/dev/null
[3.26GiB/s]
$ timeout 5 pv -r </dev/zero | pv -q --no-splice >/dev/null
[2.70GiB/s]
$ timeout 5 pv -r </dev/zero | cat >/dev/null
[2.66GiB/s]
通过配置管道大小来提高吞吐量
$ timeout 5 cat </dev/zero | pv -abC >/dev/null
10.8GiB [2.71GiB/s]
$ timeout 5 cat </dev/zero | rat | pv -abC >/dev/null # without splice
17.7GiB [3.54GiB/s]
$ timeout 5 cat </dev/zero | rat | pv -ab >/dev/null # with splice
19.4GiB [3.88GiB/s]
把它切开!
$ timeout 5 rat </dev/zero | rat | rat | rat | pv -r >/dev/null
[4.78GiB/s]
$ timeout 5 rat </dev/zero | cat | cat | cat | pv -r >/dev/null
[2.16GiB/s]
动机
我只是想把它作为 Rust 学习经验。
至少,这就是开始的原因。
我打算让 rat
几乎与 cat
相同(uutils 已经做了这些)但内置了一些额外的好处,例如
- 在任何任意的 strftime 格式中添加时间戳到行前
- 严格模式 - 在提供可能损坏的输出之前,先检测错误,例如缺失的文件/权限
- 基于模式的任何通用文本流的可读性、彩色输出(例如,红色错误,蓝色调试等)
想法/备注
-
Stdout
在 Rust 中将始终被LineWriter
包装,该包装在 新行 时刷新缓冲区。这似乎适合交互式 stdin。对于其他 I/O,请使用包装的
Vec<u8>
的缓冲区处理程序,以便在文件描述符上进行更多流控制,否则你会得到大量不必要的少量写入。 -
给定一个有尺寸的
Vec<u8>
缓冲区处理程序,在运行时性能上有一些有趣的影响即使在该向量在运行时立即被清除,与初始为空和初始填充(大小?)向量之间的行为仍然明显。看起来
read()
调用似乎从8192开始以2的幂次增长,直到达到指定的缓冲区大小,而不仅仅是传递固定数量的数据。另外,
Vec.with_capacity
也适用,并且据称无需清除,因此可以少一行代码。 -
splice(2)
与传统read()/write()调用相比,可以展示出一些惊人的性能提升然而,这些好处只有在特定条件下才能完全实现(例如,
</dev/zero >/dev/null
),这些条件不适用于常规文件的写入。与传统系统调用相比,仍有改进。可能非常适合网络套接字... -
Linux管道默认限制为64K缓冲区。它们可以增加到sysctl
fs.pipe-max-size
设置(默认为1MB)。您可以使用
fcntl()
调整管道 - 请参阅pipe(7) - Pipe Capacity和fcntl(2) - Changing the capacity of a pipe。 -
GNU
cat
在写入管道时表现出异常行为,它显然尝试写入其默认的128K缓冲区大小,从而降低了性能。仅在管道的左侧(写入)时才会发生这种情况,读取管道会立即填充和刷新64K缓冲区,正如预期的那样。在管道之间(即
echo | cat | grep -
)将执行64K的读取和写入。rat
通过使用适当的管道缓冲区大小(见上文)轻松实现~500MB-1GBps+的更多吞吐量 -
rust
io::copy
目前坚持首先使用sendfile()
,然后在同一运行时(例如,当给定多个参数时)的后续调用中使用copy_file_range()
,而且copy_file_range()
的长度远低于例如cat
(1073741824
vs9223372035781033984
) -
如何使
copy_file_range()
将文件连接多次(即每个系统调用都是追加到文件)并且在从shell追加时却不起作用(EBADF)?
上游错误修复
已知错误
请参阅rat.rs
中的TODO
依赖关系
~3.5MB
~69K SLoC