使用 Zig 实现 yes 命令

发布: 2022-11-17   上次更新: 2022-11-17   分类: 编程语言   标签: zig

文章目录

起因是看到一篇文章,作者介绍了如何用 Rust 优化 yes 命令,第一个 buffer 的版本还比较好懂,第二个复用 buffer 的就没有那么直接了。想了下用 Zig 实现会是怎么样?于是就有了下面的测试:

测试时会用到 pv 命令,需要单独安装

初始版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const std = @import("std");

pub fn main() !void {
    const argv = std.os.argv;

    const output: []const u8 = if (argv.len > 1)
        argv.ptr[1][0..std.mem.len(argv.ptr[1])]
    else
        "y";

    const out = std.io.getStdOut();
    var f = out.writer();
    // var f = std.io.bufferedWriter(out.writer());
    while ((try f.write(output)) > 0) {
        _ = try f.write("\n");
    }
}

编译运行

1
2
zig build-exe yes.zig -Drelease-safe
./yes | pv -r > /dev/null

速度大概是 [2.77MiB/s]

笔者使用机器(macOS m1)上默认的 yes 命令速率: [5.31GiB/s] ,这差距不是一点两点,下面来一步步优化。

BufferedWriter 版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const std = @import("std");

pub fn main() !void {
    const argv = std.os.argv;

    const output: []const u8 = if (argv.len > 1)
        argv.ptr[1][0..std.mem.len(argv.ptr[1])]
    else
        "y";

    const out = std.io.getStdOut();
    var f = std.io.bufferedWriter(out.writer());
    while ((try f.write(output)) > 0) {
        _ = try f.write("\n");
    }
}

速度大概是 116Mib/s

这个版本主要是利用了 bufferedWriter ,Zig 标准库默认是 4096,写入达到阈值一次性刷到底层 Writer 中,相当于变相减少了系统调用的次数。

复用 Buffer 版本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const std = @import("std");

const BUFFER_CAP = 4 * 1024;

fn fillBuffer(buf: []u8, output: []const u8) []const u8 {
    if (output.len + 1 > buf.len / 2) { // plus one newline
        return output;
    }

    std.mem.copy(u8, buf, output);
    std.mem.copy(u8, buf[output.len..], "\n");
    var buffer_size = output.len + 1;
    while (buffer_size < buf.len / 2) {
        std.mem.copy(u8, buf[buffer_size..], buf[0..buffer_size]);
        buffer_size *= 2;
    }

    return buf[0..buffer_size];
}

pub fn main() !void {
    const argv = std.os.argv;

    const output: []const u8 = if (argv.len > 1)
        argv.ptr[1][0..std.mem.len(argv.ptr[1])]
    else
        "y";

    var buffer: [BUFFER_CAP]u8 = undefined;
    const body = fillBuffer(&buffer, output);
    const stdout = std.io.getStdOut();
    var writer = stdout.writer();
    while ((try writer.write(body)) > 0) {}
}

速率大概是: [2.70GiB/s]

这个版本相比上个版本,除了减少系统调用外,通过复用同一个 buffer 减少了数据拷贝的代价。但还是赶不上系统版本的速度,可以通过增加 buffer 大小进一步提高吞吐。

当调整为 64*1024 时,速度大概是 [6.44GiB/s] ,再调大这个值速度也不会有显著提升,说明这已经达到磁盘写入上限了。

参考

评论

欢迎读者通过邮件与我交流,也可以在 MastodonTwitter 上关注我。