自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Node.js的performance鉤子和測量 API

開發(fā) 前端
Node 提供的Performance API ?不僅可以幫助確定哪些部分速度較慢,還可以幫助確定它們需要多少時間。您可以通過將這些數(shù)據(jù)作為跟蹤或指標導出到Jaeger?或 Prometheus 之類的內容來進一步探索這些數(shù)據(jù)。

當你完成了編寫和部署了項目,下一步就是去改進、消除瓶頸、提高執(zhí)行速度和優(yōu)化性能,得先了解項目現(xiàn)有的性能瓶頸和邏輯慢的地方。但是,沒有人喜歡猜測哪些部分可能更慢的試錯過程。

Node.js 提供了各種內置性能鉤子函數(shù)來衡量執(zhí)行速度,找出代碼的哪些部分值得優(yōu)化,并收集應用程序代碼執(zhí)行的精細視圖。

在本文中,您將學習如何使用 Node.js 性能鉤子函數(shù)和測量 API 來識別瓶頸并增強應用程序的性能,從而加快響應時間并提高資源效率。

Node.js的Performance API  概述

首先要了解為什么以及何時應該使用 Node 提供的 Performance API以及它提供的各種選項,考慮這樣一種情況:您想要測量特定代碼塊的執(zhí)行時間。

為此,您可能已經使用了 Date 對象,如下所示:

let start = Date.now();
for (let i = 0; i < 10000; i++) { } // stand-in for some complex calculation
let end = Date.now();
console.log(end - start);

但是,如果您運行上述操作并觀察,您會注意到這還不夠精確。

例如,像上面這樣的空循環(huán)會將 0 或 1 記錄為差值,并且不會給我們足夠的粒度。Date 類只能提供毫秒級的粒度,如果代碼以 100 納秒的順序運行,這不會給我們正確的測量結果。

為此,我們可以改用 Performance API 來獲得更好的測量結果:

const {performance} = require('node:perf_hooks');

let start = performance.now()
for (let i = 0; i < 10000; i++) {}
let end = performance.now()

console.log(end - start);

這樣,我們就可以得到一個更精細的值,在我的系統(tǒng)上,該值在 0.18 到 0.21 毫秒的范圍內,精度高達 15-16 位小數(shù)。這是我們可以使用 Node Performance API 更好地測量執(zhí)行時間的一種簡單方法。

該 API 還提供了一種在程序運行期間精確標記時間點的方法。我們可以使用performance.mark方法獲取高精度事件的時間戳,例如循環(huán)迭代的開始時間。

運行下面代碼:

let start_mark = performance.mark("loop_start",
  {detail:"starting loop of 1000 iterations"}
);

for (let i = 0; i < 10000; i++) {}

let end_mark = performance.mark("loop_end",
  {detail:"ending loop of 1000 iterations"}
);

console.log( start_mark, end_mark );

輸出:

PerformanceMark {
  name: 'loop_start',
  entryType: 'mark',
  startTime: 27.891528000007384,
  duration: 0,
  detail: 'starting loop of 1000 iterations'
}
PerformanceMark {
  name: 'loop_end',
  entryType: 'mark',
  startTime: 28.996093000052497,
  duration: 0,
  detail: 'ending loop of 1000 iterations'
}

mark 函數(shù)將標記的名稱作為第一個參數(shù)。第二個參數(shù)對象中的detail允許提供有關該標記的額外詳細信息,例如運行的迭代次數(shù)、數(shù)據(jù)庫查詢參數(shù)等。

然后,可以使用 mark 函數(shù)返回的對象通過 Prometheus exporter sdk 將計時數(shù)據(jù)導出到 Prometheus 之類的東西。這允許我們在應用程序外部查詢和可視化耗時信息。由于 mark 是一個瞬時時間點,因此返回對象中的 duration 字段始終為零。

而不是手動調用 performance.now 并計算兩個事件之間的差異,我們可以使用 marks 和 measure 函數(shù)執(zhí)行相同的操作。我們可以使用上面標記的名稱來測量兩個標記之間的持續(xù)時間:

performance.mark("loop_start",
  {detail:"starting loop of 1000 iterations"}
);

for (let i = 0; i < 10000; i++) {}

performance.mark("loop_end",
  {detail:"ending loop of 1000 iterations"}
);

console.log(performance.measure("loop_time","loop_start","loop_end"));

measure 的第一個參數(shù)是我們要為測量指定的名稱。然后,接下來的兩個參數(shù)分別指定要開始和結束測量的標記的名稱。

這兩個參數(shù)都是可選的 — 如果兩者都沒有給出,則為 performance.measure 將返回應用程序啟動和測度調用之間經過的時間。如果我們只提供第一個參數(shù),該函數(shù)將返回性能之間經過的performance.mark替換為該名稱和 measure 調用。

如果兩者都提供,該函數(shù)將返回它們之間的高精度時間差。對于上面的示例,我們將得到如下輸出:

PerformanceMeasure {
  name: 'loop_time',
  entryType: 'measure',
  startTime: 27.991639000130817,
  duration: 1.019368999870494
}

這可以再次與 Prometheus exporter 一起使用,以便導出自定義測量指標。如果您的設置執(zhí)行藍綠或 Canary 部署,則可以比較舊版本和新版本的性能,以查看您的優(yōu)化是否按預期工作。

最后,需要注意的一點是,Performance API 在內部使用固定大小的緩沖區(qū)來存儲標記和度量,因此我們需要在使用完它們后對其進行清理。這可以使用以下方法完成:

performance.clearMarks("mark_name");

或者:

performance.clearMeasures("measure_name");

這些函數(shù)將從相應的緩沖區(qū)中刪除具有給定名稱的標記/度量。如果在不提供任何參數(shù)的情況下調用這些函數(shù),它們將清除緩沖區(qū)中存在的所有標記/度量,因此在沒有任何參數(shù)的情況下調用這些函數(shù)時要小心。

使用 Performance鉤子優(yōu)化您的應用

現(xiàn)在讓我們看看如何使用這個 API 來優(yōu)化我們的應用程序。在我們的示例中,我們將考慮從數(shù)據(jù)庫中獲取一些數(shù)據(jù),然后手動排序并將其返回給用戶的情況。

我們想了解每個操作需要多少時間,以及首先優(yōu)化的最佳位置是什么。為此,我們將首先測量發(fā)生的各種事件:

async function main(){
    const querySize = 10; // ideally this will come from user's request

    performance.mark("db_query_start",{detail:`query size ${querySize}`});
    const data = fetchData(querySize);
    performance.mark("db_query_end",{detail:`query size ${querySize}`});

    performance.mark("sort_start",{detail:`sort size ${querySize}`});
    const sorted = sortData(data);
    performance.mark("sort_end",{detail:`sort size ${querySize}`});

    console.log(performance.measure("db_time","db_query_start","db_query_end"));
    console.log(performance.measure("sort_time","sort_start","sort_end"));

    // clear the marks...
}

我們首先聲明查詢大小,在實際應用程序中,它可能來自用戶的請求。

然后我們使用performance.mark 函數(shù)來標記數(shù)據(jù)庫獲取和排序操作的開始和結束。最后,我們使用 performance 輸出這些事件之間的持續(xù)時間。量功能。我們得到這樣的輸出:

PerformanceMeasure {
  name: 'db_time',
  entryType: 'measure',
  startTime: 27.811830999795347,
  duration: 1.482880000025034
}
PerformanceMeasure {
  name: 'sort_time',
  entryType: 'measure',
  startTime: 29.31366699980572,
  duration: 0.09800400026142597
}

要查看這兩個操作在查詢大小增加時的表現(xiàn),我們將更改查詢大小值并記下度量值。在我的系統(tǒng)上,我得到以下內容:

正如我們在這里看到的,隨著查詢大小的增加,排序時間會迅速增加,首先優(yōu)化它可能更有益。通過使用一些不同的排序算法,我們得到以下內容:

雖然對于非常小的查詢大小,排序時間略短,但與原始測量值相比,時間增長緩慢。因此,如果我們期望經常處理大型查詢,那么在此處更改排序算法將是有益的。

同樣,我們可以測量在查詢字段上創(chuàng)建索引之前和之后數(shù)據(jù)庫獲取時間的差異。然后我們可以決定索引創(chuàng)建是否有用,或者哪些字段在用于索引時提供更多好處。

使用后臺工作程序卸載任務

在創(chuàng)建基于 UI 的應用程序時,我們需要 UI 能夠響應,即使正在進行一些繁重的處理任務也是如此。如果在處理大數(shù)據(jù)時 UI 凍結,則處理起來將是一種糟糕的用戶體驗。在網站上,這可以使用 Web Worker 來完成。

對于直接使用 Node 運行的應用程序,我們可以使用 Node 的 worker_threads 模塊將計算密集型任務卸載到后臺線程。

請注意,僅當任務是 CPU 密集型任務(例如排序或解析數(shù)據(jù))時,這才有用。如果任務依賴于 I/O,例如讀取文件或獲取網絡資源,則使用 Node 的 async-await 比使用 worker 更有效。

我們可以按如下方式創(chuàng)建和使用 worker:

const { Worker, isMainThread, parentPort, workerData, } = 
    require("node:worker_threads");

async function main() {
  const data = await fetchData(10);
  let sorted = await new Promise((resolve, reject) => {
    const worker = new Worker(__filename, {
      workerData: data,
    });
    worker.on("message", resolve);
    worker.on("error", reject);
    worker.on("exit", (code) => {
      if (code !== 0)
        reject(new Error(`Worker stopped with exit code ${code}`));
    });
  });
}
function worker() {
  const data = workerData;
  sortData(data);
  parentPort.postMessage(data);
}

if (isMainThread) {
  // we are in the main thread of our application
  main().then(() => {
    console.log("done");
  });
} else {
  // we are in the background thread spawned by the main thread
  worker();
}

我們首先從 worker_threads 模塊導入所需的函數(shù)和變量聲明。然后我們定義兩個函數(shù) —main(將在主線程中運行)和 worker (將在 worker 線程中運行)。

然后,我們檢查腳本是作為主線程還是作為 worker 線程執(zhí)行,并相應地調用 main/worker 函數(shù)。為了簡單起見,我們在一個文件中定義了所有這些函數(shù),但我們也可以在它們自己的文件中分離出 main 和 worker 函數(shù)。

在 main 函數(shù)中,我們像以前一樣獲取數(shù)據(jù)。然后我們創(chuàng)建一個 Promise,并在其中創(chuàng)建一個新的 worker。Worker 構造函數(shù)需要一個文件路徑,該路徑將作為Worker線程運行。

這里我們使用 __filename builtin 給它相同的文件。在第二個參數(shù)中,我們將要排序的數(shù)據(jù)作為 workerData 傳遞。此 workerData 將由 Node 運行時提供給 worker 線程。

最后,我們監(jiān)聽來自 worker 的事件 — 收到消息時,我們解決 promise,如果出現(xiàn)錯誤或非零退出代碼,我們拒絕 promise。

在 worker 線程中,我們從變量 workerData 中獲取從主線程傳遞的數(shù)據(jù),該變量是從 worker_threads 模塊導入的。在這里,我們對它進行排序,并將一條消息發(fā)布到包含排序數(shù)據(jù)的主線程。

在主線程中,我們可以將其保存在隊列中或定期檢查它,而不是立即等待 promise。這樣,當worker線程進行排序計算時,我們可以保持主線程的響應性。我們還可以從 worker 線程發(fā)送中間消息,指示排序進度。

優(yōu)化 Node 應用程序的常見提示

雖然每個應用程序都有自己的性能優(yōu)化方法,但Node.js應用程序有一些常見的起點。

優(yōu)化前觀察

在開始優(yōu)化應用程序之前,您必須檢測和測量應用程序的性能,以便您可以準確了解哪些函數(shù)或 API/DB 調用需要優(yōu)化。

嘗試進行盲目優(yōu)化可能會降低性能,這就是為什么使用 Node 提供的性能鉤子和 API 進行測量是一個很好的起點。

有一種簡單的方法來重復測量

要確定您的優(yōu)化是否有效,您應該有一種方便的方法來衡量之前和之后的性能。

這可以通過擁有兩個構建來完成 —--- 一個有更改,一個沒有更改,有一個運行測試和測量的腳本,以及可以為您提供比較的東西。為更改提供明確的前后性能值可以幫助您確定這些更改是否值得。

嘗試為數(shù)據(jù)庫編制索引并緩存請求/響應

如果您的應用程序使用數(shù)據(jù)庫并頻繁查詢,則應考慮在查詢的參數(shù)上創(chuàng)建索引,以提高檢索性能。

這將以可能增加存儲大小和可能增加插入/更新查詢時間為代價,因此您應該仔細衡量使用案例中的前后,并確定權衡是否良好。

提高性能的另一種方法是使用一些緩存方案,以便快速響應數(shù)據(jù)庫或 API 查詢。如果您可以使用查詢參數(shù)緩存 API 響應,然后使用此緩存響應以后的請求,則可以有效地使用它。

請注意,緩存是一把雙刃劍。您需要仔細評估保留緩存條目的時間、逐出條目的依據(jù)以及何時使緩存失效。錯誤地執(zhí)行此操作不僅會降低您的性能,而且還有可能在用戶之間發(fā)送不正確的數(shù)據(jù)或泄露的數(shù)據(jù)。

減少依賴性

如果您曾經查看 node_modules 或檢查過node_modules 所占用的磁盤大小,您就會知道 Node 項目中的依賴關系有多嚴重。

添加新的依賴項時需要小心,因為它們可能會添加更多的傳遞依賴項,而解析所有這些依賴項可能會影響應用程序的啟動性能。您可以嘗試通過以下方法緩解此問題:

  • 刪除未使用的軟件包 — 有時軟件包中有多個軟件包。JSON 格式這些 ID 不再在應用程序中使用,可以刪除。這對于縮小依賴項的數(shù)量和軟件包的構建大小非常有用
  • 使用打包器對tree-shaking并從最終構建中刪除未使用的模塊 — 在捆綁和打包應用程序時,您可以使用捆綁器提供的功能從依賴項中刪除未使用的模塊。您只保留代碼使用的依賴項部分,而不將其余部分包含在最終構建中
  • 從依賴項中提供所需的特定代碼 — 當您只需要代碼的一小部分時,而不是將整個包添加為依賴項,而是提供代碼的特定部分。執(zhí)行此操作時,請務必檢查并遵守原始代碼的許可證
  • 延遲加載依賴項 — 您可以延遲加載依賴項以提高啟動性能,并在不需要該依賴項的情況下減少內存使用量

結論

Node 提供的Performance API 不僅可以幫助確定哪些部分速度較慢,還可以幫助確定它們需要多少時間。您可以通過將這些數(shù)據(jù)作為跟蹤或指標導出到Jaeger或 Prometheus 之類的內容來進一步探索這些數(shù)據(jù)。

請記住 — 擁有大量數(shù)據(jù)只會使其更難探索,因此一個好的策略是首先只測量粗略事件的時間,例如函數(shù)調用甚至請求的端到端處理,然后為花費最多時間的函數(shù)添加越來越多的細粒度測量。

原文地址:https://blog.logrocket.com/node-js-performance-hooks-measurement-apis-optimize-applications/

原文作者:Yashodhan Joshi

本文譯者:一川

責任編輯:姜華 來源: 宇宙一碼平川
相關推薦

2022-02-05 21:15:59

Node.jsrequire函數(shù)

2022-09-04 15:54:10

Node.jsAPI技巧

2023-01-10 14:11:26

2022-03-08 15:13:34

Fetch APINode.js開發(fā)者

2023-04-18 15:18:10

2013-11-01 09:34:56

Node.js技術

2015-03-10 10:59:18

Node.js開發(fā)指南基礎介紹

2019-07-09 14:50:15

Node.js前端工具

2021-08-20 09:00:00

Node.js開發(fā)API

2020-05-29 15:33:28

Node.js框架JavaScript

2012-02-03 09:25:39

Node.js

2021-12-25 22:29:57

Node.js 微任務處理事件循環(huán)

2014-02-19 16:28:53

Node.jsWeb工具

2019-02-15 10:49:37

Node.jsweb服務器

2020-09-28 06:57:39

Node.jsGraphQLAPI

2021-09-01 13:32:48

Node.jsAPI POSIX

2011-09-09 14:23:13

Node.js

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2011-09-02 14:47:48

Node
點贊
收藏

51CTO技術棧公眾號