#跨平台 #命令行工具 #实用工具 #核心工具 #文件I/O #文本文件 #CLI 文件

应用 ratiscat

ratiscat 是在 Rust 中重新实现的 cat,并添加了一些新功能

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 命令行工具

MIT 许可证

21KB
161

ratiscat

rat 是 Rust 中高性能的 cat 重新实现

结果证明,老鼠比猫更适合管道。

Baba is You Cat is Jelly

请参阅 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

pvuu-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 Capacityfcntl(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()的长度远低于例如cat1073741824 vs 9223372035781033984

    已报告并修复:https://github.com/rust-lang/rust/issues/114341

  • 如何使copy_file_range()将文件连接多次(即每个系统调用都是追加到文件)并且在从shell追加时却不起作用(EBADF)?

上游错误修复

已知错误

请参阅rat.rs中的TODO

依赖关系

~3.5MB
~69K SLoC