在開發 Node.js 應用程式時,效能瓶頸往往不易察覺。幸運的是,Node.js 提供了內建的 Profiler,可以幫助我們找出程式的效能瓶頸並進行優化。本文將分享我在使用 Node.js Profiler 進行效能優化的經驗。

如何使用 Node.js Profiler

可以使用 node --inspect-brk 啟動應用程式,並透過 Chrome DevTools 進行分析:

node --inspect-brk somefile.js

執行後打開 Chrome DevTools 點擊 DevTools for Node,進入 Performance 面板,點擊錄製按鈕來進行效能分析。完成後會產生一張 Flame Graph,顯示各個函數的執行時間與呼叫堆疊。

Flame Graph 提供兩種常見檢視方式:

範例:優化 eslint-plugin-react

eslint-plugin-react 為例,這是一個用於 React 的 ESLint 插件。我們在進行分析時,使用如下指令啟動 Profiler:

node --inspect-brk node_modules/.bin/eslint .

在報告中,我發現名為 isCreateElement 的函數佔用了約 5% 的執行時間,這成為潛在的優化目標:

Screenshot 2025-02-04 at 10.48.55 PM.png

image.png

優化過程

檢視原始碼後發現,isCreateElement 函數在每次呼叫時都進行了一次昂貴的運算 pragmaUtil.getFromContext(context)

module.exports = function isCreateElement(node, context) {
  const pragma = pragmaUtil.getFromContext(context);
  if (
    node.callee &&
    node.callee.type === 'MemberExpression' &&
    node.callee.property.name === 'createElement' &&
    node.callee.object &&
    node.callee.object.name === pragma
  ) {
    return true;
  }
};

我們將 pragmaUtil.getFromContext(context) 的呼叫直接移到條件判斷的尾端,讓他有機會被 short-circuit 避免執行:

module.exports = function isCreateElement(node, context) {
  if (
    node.callee &&
    node.callee.type === 'MemberExpression' &&
    node.callee.property.name === 'createElement' &&
    node.callee.object &&
    node.callee.object.name === pragmaUtil.getFromContext(context)
  ) {
    return true;
  }
};

優化結果