起因是看到一篇文章,作者介绍了如何用 Rust 优化 yes 命令,第一个 buffer 的版本还比较好懂,第二个复用 buffer 的就没有那么直接了。想了下用 Zig 实现会是怎么样?于是就有了下面的测试:
测试时会用到 pv 命令,需要单独安装
初始版
1const std = @import("std");
2
3pub fn main() !void {
4 const argv = std.os.argv;
5
6 const output: []const u8 = if (argv.len > 1)
7 argv.ptr[1][0..std.mem.len(argv.ptr[1])]
8 else
9 "y";
10
11 const out = std.io.getStdOut();
12 var f = out.writer();
13 // var f = std.io.bufferedWriter(out.writer());
14 while ((try f.write(output)) > 0) {
15 _ = try f.write("\n");
16 }
17}编译运行
1zig build-exe yes.zig -Drelease-safe
2./yes | pv -r > /dev/null速度大概是 [2.77MiB/s]
笔者使用机器(macOS m1)上默认的 yes 命令速率: [5.31GiB/s] ,这差距不是一点两点,下面来一步步优化。
BufferedWriter 版本
1const std = @import("std");
2
3pub fn main() !void {
4 const argv = std.os.argv;
5
6 const output: []const u8 = if (argv.len > 1)
7 argv.ptr[1][0..std.mem.len(argv.ptr[1])]
8 else
9 "y";
10
11 const out = std.io.getStdOut();
12 var f = std.io.bufferedWriter(out.writer());
13 while ((try f.write(output)) > 0) {
14 _ = try f.write("\n");
15 }
16}速度大概是 116Mib/s 。
这个版本主要是利用了 bufferedWriter ,Zig 标准库默认是 4096,写入达到阈值一次性刷到底层 Writer 中,相当于变相减少了系统调用的次数。
复用 Buffer 版本
1const std = @import("std");
2
3const BUFFER_CAP = 4 * 1024;
4
5fn fillBuffer(buf: []u8, output: []const u8) []const u8 {
6 if (output.len + 1 > buf.len / 2) { // plus one newline
7 return output;
8 }
9
10 std.mem.copy(u8, buf, output);
11 std.mem.copy(u8, buf[output.len..], "\n");
12 var buffer_size = output.len + 1;
13 while (buffer_size < buf.len / 2) {
14 std.mem.copy(u8, buf[buffer_size..], buf[0..buffer_size]);
15 buffer_size *= 2;
16 }
17
18 return buf[0..buffer_size];
19}
20
21pub fn main() !void {
22 const argv = std.os.argv;
23
24 const output: []const u8 = if (argv.len > 1)
25 argv.ptr[1][0..std.mem.len(argv.ptr[1])]
26 else
27 "y";
28
29 var buffer: [BUFFER_CAP]u8 = undefined;
30 const body = fillBuffer(&buffer, output);
31 const stdout = std.io.getStdOut();
32 var writer = stdout.writer();
33 while ((try writer.write(body)) > 0) {}
34}速率大概是: [2.70GiB/s]
这个版本相比上个版本,除了减少系统调用外,通过复用同一个 buffer 减少了数据拷贝的代价。但还是赶不上系统版本的速度,可以通过增加 buffer 大小进一步提高吞吐。
当调整为 64*1024 时,速度大概是 [6.44GiB/s] ,再调大这个值速度也不会有显著提升,说明这已经达到磁盘写入上限了。