对 Node.js 程序进行性能分析
2022-11-20 / UNIDY
这个暑假,我用 Node.js 将 Saiblo 评测机重写了一遍。但测试时,我遇到了一些 IO 性能问题。
出于一些安全性与保密性的原则,我在这里并不介绍具体的问题,而是简单记录一下我分析问题时的心路历程,以便后续回顾。
寻找 IO 代码
Node.js 最典型的特征是事件驱动 IO,它的底层 IO 采用 libuv 实现。
在本项目中,我需要重点关注文件读写的性能。以写文件(write
)为例,经过层层搜索,我找到了 Node.js 里写文件的 C++ 实现。其中,调用 libuv 接口的异步代码是:
1 | if (req_wrap_async != nullptr) { // write(fd, buffer, off, len, pos, req) |
我们再看一下 AsyncCall
相关定义,就会明白,这里执行的代码相当于:
1 | uv_fs_write(env()->event_loop(), req(), fd, &uvbuf, 1, pos, AfterInteger); |
其中,AfterInteger
是回调函数,用于处理 write 的返回值:
1 | void AfterInteger(uv_fs_t* req) { |
进行性能分析
就当我准备自己插装代码,分析 IO 耗时,寻找性能瓶颈时,我注意到了这两个宏:
1 | FS_ASYNC_TRACE_BEGIN0(UV_FS_WRITE, req_wrap_async); |
难道 Node.js 本身就插装了性能追踪代码?
经过进一步分析和寻找,我发现果然如此,并找到了相关文档。
真是太棒了,我只要指定事件类别("node.fs.async"
),即可轻松完成性能追踪。
当然,由于 Saiblo 评测机是多进程执行的,我只需要追踪其中一个子进程的 IO 事件。这里简单记录一下代码,以便后续回顾:
1
2
3
4
5
6
7
8
9
10
11
12
13 // ...
import trace from "node:trace_events";
// ...
const main = async () => {
if (process.argv[2] === "worker") {
const tracing = trace.createTracing({ categories: ["node.fs.async"] });
tracing.enable();
// ...
} else {
// ...
}
};
阅读追踪日志
但当我拿到日志文件时,我傻眼了,因为它是以人类几乎不可读的 JSON 格式记录的。
当然,Node.js 的文档里写得很清楚,在 Chrome 浏览器中访问 chrome://tracing
即可以可视化的方式加载追踪日志。然而,我没有 Chrome 浏览器,那可怎么办呢?
经过在网上搜索,我找到了谷歌写的一个网站 Perfetto UI,与 chrome://tracing
有着同样的功能。有了这个网站,我成功完成了 IO 性能的分析。
本文链接:https://www.unidy.cn/articles/node-tracing/