过去几个月,作者对 Zig 编程语言和以太坊加密货币这两项技术感到好奇,并用 Zig 编写了以太坊虚拟机的字节码解释器。Zig 擅长性能优化,能精细控制内存和控制流,作者用它来与官方 Go 实现的以太坊进行基准测试,开始时 Zig 实现的性能比 Go 实现差约 40%。
后来作者对基准测试脚本进行简单重构后,应用性能下降,通过比较zig build run
和直接运行编译后的二进制文件,发现额外的构建步骤使程序运行快了近 10 倍,这让作者很困惑。
为调试性能之谜,作者简化应用为只计算从 stdin 读取的字节数的程序,仍能看到性能差异,zig build run
只需 13 微秒,而直接运行编译后的二进制文件需 162 微秒。作者的测试是一个包含echo
、xxd
和zig build run
的 bash 管道,差异在于zig build run
会重新编译应用,而直接运行编译后的二进制文件则不会。
作者在 Ziggit 论坛发帖询问,起初得到的回复说有“输入缓冲”问题但没有具体建议,Zig 的创始人 Andrew Kelly 指出作者犯了另一个性能错误,即每次读取字节都进行一次系统调用。后来作者的朋友 Andrew Ayer 解释了原因,是因为 bash 管道中的命令是并行执行的,zig build run
在编译时xxd
已经开始,所以count-bytes
开始时输入已准备好,而直接运行编译后的二进制文件时count-bytes
要等待xxd
填充管道缓冲区。
作者意识到自己对 bash 管道的思维模型是错误的,通过写简单的 bash 脚本证明了管道中的命令是同时开始的。理解这一点后,作者对字节计数器的行为有了合理的解释。
作者通过将准备阶段和执行阶段分开,使基准测试从 438 微秒下降到 67 微秒。按照 Andrew Kelly 的建议使用缓冲读取器,性能又提高了 16%。对更大输入的基准测试表明,缓冲输入读取使 Zig 实现比官方以太坊实现快约 2 倍。作者还通过使用固定缓冲区分配器作弊,在知道最大内存需求的情况下,使以太坊实现比官方 Go 实现快 3 倍。
作者的经验是要尽早经常进行性能基准测试,将基准测试纳入持续集成并存档结果,便于识别测量变化的原因,同时要理解自己的指标,避免因忽略某些因素而导致错误。源代码为eth-zvm,是用 Zig 实现的业余以太坊虚拟机。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。