美文网首页
【转】解读 Nodejs 性能 API:Performance

【转】解读 Nodejs 性能 API:Performance

作者: 囍冯总囍 | 来源:发表于2020-08-05 09:37 被阅读0次

    转载自大专栏

    《解读 Nodejs 性能 API:Performance Timing》

    前言

    本周末,我花了一点时间去查看与学习 Nodejs v8.5.0 更新的内容,其中一点就包括了与性能时间相关的 API,看起来感觉还是比较有趣的。

    在 Nodejs v8.5.0 里新增了与性能时间相关的 API:Performance Timing。它提供了 W3C Performance Timeline 规范的实现,该 API 目的是支持高精度性能指标的收集,保存和获取性能相关的度量数据。

    Performance Timing 是基于 libuv 内的高精度时间模块 uv_hrtime 来实现。

    image

    下面简单粗糙的介绍 Performance Timing

    Performance

    Performance Timing 被放置到 perf_hooks 模块里:

    const {
        performance,
        PerformanceObserver
    } = require('perf_hooks');
    

    Performance 对象的作用是提供对 “性能时间表” 的访问,这是由 Node.js 进程和用户代码维护的。
    在内部,性能时间表只不过是一个 PerformanceEntry 对象的数组,通常它们是按照顺序来存储。

    PerformanceEntry 对象最基本的结构包括了:

    PerformanceEntry {
        duration: 217.711151,         
        startTime: 1944738.703347,    // 开始记录时的时间戳
        entryType: 'function',        // 类型
        name: 'myFunction'            // 名称
    }
    

    PerformanceEntry 可以指定多种类型,目前仅支持:nodeframemarkmeasuregc,和 function

    Nodejs 进程时间

    当 Nodejs 应用启动时,Nodejs 会默认添加几个 PerformanceEntry 实例到 “性能时间表” 里。
    而第一个 PerformanceEntry 实例是用于记录 Nodejs 进程启动时的性能参数。

    可以通过 perf_hooks.performance.nodeTiming 来访问它。

    目前可以看到的属性包括了:

    > perf_hooks.performance.nodeFrame
    PerformanceNodeTiming {
        duration: 4512.380027,                          // 进程已激活的毫秒数
        startTime: 158745518.63114,
        entryType: 'node',
        name: 'node',
        arguments: 158745518.756349,                    // 命令行参数处理完成的时间戳
        initialize: 158745519.09161,                    // Nodejs 完成初始化的时间戳
        inspectorStart: 158745522.408488,               // Nodejs 检查器启动完成的时间戳
        loopStart: 158745613.442409,                    // Nodejs 事件循环开始的时间戳
        loopExit: 0,                                    // Nodejs 事件循环退出的时间戳
        loopFrame: 158749857.025862,                    // Nodejs 事件循环的当前迭代开始的时间戳
        bootstrapComplete: 158745613.439273,            // Nodejs 引导过程完成的时间戳。
        third_party_main_start: 0,                      // third party main 处理开始的时间戳 [没有时为 0]
        third_party_main_end: 0,                        // third party main 处理开始的时间戳
        cluster_setup_start: 0,                         // 集群设置启动的时间戳
        cluster_setup_end: 0,                           // 集群设置结束的时间戳
        module_load_start: 158745583.850295,            // 主模块加载开始的时间戳
        module_load_end: 158745583.851643,              // 主模块加载结束的时间戳
        preload_modules_load_start: 158745583.852331,   // 启动预加载模块加载的时间戳
        preload_modules_load_end: 158745583.879369      // 预加载模块加载结束的时间戳
    }
    

    事件循环时序

    当 Nodejs 应用启动时,Nodejs 自动添加到 “性能时间表” 的第二个实例是用于记录 Nodejs 事件循环的时序。

    可以通过 perf_hooks.performance.nodeFrame 来访问它。

    目前可以看到的属性包括了:

    > perf_hooks.performance.nodeFrame
    PerformanceFrame {
        countPerSecond: 9.91151849696801,  // 每秒事件循环迭代次数
        count: 68,                         // 事件循环迭代的总数
        prior: 0.124875,                   // 事件循环的上一次迭代所用的总毫秒数
        entryType: 'frame',
        name: 'frame',
        duration: 128.827398,              // 当前事件循环持续时间
        startTime: 32623025.740256         // 事件循环的当前迭代开始的时间戳
    }
    

    用标志来测量

    我们知道,可以使用 setTimeout 来做一些延迟操作,比如在一秒后执行某个函数:setTimeout(myFunction, 1000)

    但,事实上真的是 1000ms 后执行吗?

    使用 performance 提供的 mark,可以轻易的计算出时间差

    const {
        performance,
    } = require('perf_hooks');
    
    performance.mark('A');          // 标志一下
    
    setTimeout(() => {
        performance.mark('B');      // 再标记一下
    
        // performance.measure(name, startMark, endMark): 在性能时间表里添加一项
        performance.measure('A to B', 'A', 'B');
    
        // PerformanceEntry 对象
        const entry = performance.getEntriesByName('A to B')[0];
    
        console.log(entry);
        /*
            输出结果:
            PerformanceEntry {
                duration: 1002.693559,
                startTime: 4259805.238914,
                entryType: 'measure',
                name: 'A to B'
            }
        */
    }, 1000);
    

    你会发现,它多出了 2ms,哇哇坑坑的。

    测量函数的执行时间

    还可以使用 PerformanceObserver 订阅 'function' 类型。每次调用包装函数时,时序数据将自动添加到 “性能时间表”。

    下面通过简单的示例来测量函数的执行时间。

    const {
        performance,
        PerformanceObserver
    } = require('perf_hooks');
    
    let result = [];
    
    function (n) {
        if (n == 1 || n == 2) {
            return 1;
        }
        return fib(n - 1) + fib(n - 2);
    }
    
    // timerify: 在一个新函数中包装一个函数,用于测量包装函数的运行时间
    const myfib = performance.timerify(fib);
    
    // 有对象添加到性能时间表时,发出通知
    const obs = new PerformanceObserver((list, observer) => {
        // PerformanceEntry 对象列表
        const entries = list.getEntries();
    
        entries.forEach((entry, index) => {
            // entry[0] 是第一个参数的值,如此类推
            console.log(`${entry.name}(${entry[0]}) = ${result[index]}, run time:`, entry.duration, 'ms');
        });
    
        // 断开 PerformanceObserver 实例与所有通知的连接。
        obs.disconnect();
        // 从性能时间表中清除所有对象
        performance.clearFunctions();
    });
    
    // buffered 为 true 表示异步通知,默认是同步通知
    obs.observe({ entryTypes: ['function'], buffered: true });
    
    result.push(
        myfib(5),
        myfib(10),
        myfib(20),
        myfib(30),
    );
    
    /*
        输出结果:
        fib(5) = 5, run time: 0.001568 ms
        fib(10) = 55, run time: 0.005568 ms
        fib(20) = 6765, run time: 2.007623 ms
        fib(30) = 832040, run time: 7.03683 ms
    */
    

    可以看到,进度到达了微秒级别。

    测量模块加载时间

    还可以使用一种特别有趣的方式是测量 Nodejs 应用中为每个模块依赖关系加载时间:

    const {
        performance,
        PerformanceObserver
    } = require('perf_hooks');
    
    const mod = require('module');
    
    // 依赖模块
    mod.Module.prototype.require =
    performance.timerify(mod.Module.prototype.require);
    
    require = performance.timerify(require);
    
    const obs = new PerformanceObserver((list, observer) => {
        const entries = list.getEntries();
    
        entries.forEach((entry) => {
            console.log(`require('${entry[0]}')`, entry.duration, 'ms');
        });
    
        obs.disconnect();
        performance.clearFunctions();
    });
    
    obs.observe({ entryTypes: ['function'], buffered: true });
    
    const fetch = require('node-fetch');
    
    /*
        输出结果:
        require('node-fetch') 32.271811 ms
        require('url') 0.006979 ms
        require('url') 0.005748 ms
        require('http') 6.021816 ms
        require('https') 8.655848 ms
        require('zlib') 1.569498 ms
        require('stream') 0.00739 ms
        require('./lib/body') 11.224192 ms
        require('encoding') 5.052119 ms
        require('iconv-lite') 3.416933 ms
        require('buffer') 0.005747 ms
        require('./bom-handling') 0.550125 ms
        require('./streams') 0.641265 ms
        require('buffer') 0.004927 ms
        require('stream') 0.002463 ms
        require('./extend-node') 0.714342 ms
        require('buffer') 0.004927 ms
    
        ... 省略 n 个
    */
    

    测量 GC 活动的情况

    使用 entryTypes: ['gc'] 可以测量 GC 活动的情况。
    observe 后,每次 GC 活动时,都会添加到性能时间表里。

    const {
        performance,
        PerformanceObserver
    } = require('perf_hooks');
    
    
    const obs = new PerformanceObserver((list) => {
        console.log(list.getEntries());
        performance.clearGC();
    
        /*
            输出结果:
            [
                PerformanceEntry {
                    duration: 0.89498,         // gc 持续了 0.89498 ms
                    startTime: 6039754.909044,
                    entryType: 'gc',
                    name: 'gc',
                    kind: 1
                }
            ]
        */
    });
    
    obs.observe({ entryTypes: ['gc'] });
    

    细节可能会改变

    API Performance Timing 是 v8.5.0 新增的,目前稳定性为 1,具体的细节可能会改变,敬请留意。

    参考资料

    相关文章

      网友评论

          本文标题:【转】解读 Nodejs 性能 API:Performance

          本文链接:https://www.haomeiwen.com/subject/tnknrktx.html