Node.jsにおける高精度タイミング：実践方法

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/787ebc73-770e-154e-7a28-e8d8a63d7b69.png)](https://leapcell.io/?lc_t=q_hrtime)

Node.js のコードの実行時間をテストする場合、どの時間計測関数を選びますか？多くの場合、まず思い浮かぶのは `Date.now` や `Date.getTime` でしょう。

**結論を先に述べると：**

Node.js では、以下の順序で時間計測関数を優先的に選択することをお勧めします：

1. **`process.hrtime`**
2. **`performance.now`**
3. **`Date.now`**

この選択は、**精度** と **クロック同期** の観点から決定されています。

## 解説

### `Date.now` の制限

1. 返される時間の精度はミリ秒 (10^-3) レベルで、多くの用途では十分ではありません。
2. システム時間に依存しており、他のソフトウェアの調整による影響を受ける可能性があります。

より高い精度とシステム時間からの独立性を実現するために、W3C は [High Resolution Time Level 2](https://www.w3.org/TR/hr-time-2/) 標準を制定しました。この標準の [6. Monotonic Clock](https://www.w3.org/TR/hr-time-2/#sec-monotonic-clock) の章では、単調増加するグローバルシステムクロックの提供が規定されています。

![aae8e25d-20a7-4cdf-8f6d-68982a60f7fe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/4493bb2f-be77-f341-3255-e604092d22c1.png)

この標準は、Node.js およびブラウザで `performance` オブジェクトとして実装されています。以下のように [`performance.now`](https://nodejs.org/api/perf_hooks.html#perf_hooks_performance_now) を使用して、基準点からのタイムスタンプを取得できます。この関数には次のような特徴があります：

1. 他の JavaScript の時間関連関数（例：`Date.now`）とは異なり、`performance.now()` は浮動小数点数を返し、マイクロ秒 (10^-6) レベルの精度を実現しています。
2. 時間は **一定の速度で増加** し、システム時間の調整（他のソフトウェアの影響など）を受けません。
3. 標準の定義により、**クロックドリフト** が許容されています。

![53e7e594-6f56-4fab-97a1-1a52ce1e6ac0.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/9ddba943-bd85-51e9-edf3-5a48827a5a23.png)

> ### クロックドリフトについての簡単な説明
>
> クロックドリフトは、**クロック同期** の概念に由来します。クロック同期とは、複数の独立した時計を調整して一致させることを目的とする技術です。実際には、同期後も時計の進み方にわずかな差異があるため、一定期間後には表示される時間にズレが生じることがあります。

### より高精度なクロックはあるか？

はい、Node.js には [`process.hrtime`](http://nodejs.cn/api/process/process_hrtime_time.html) というメソッドがあります。このメソッドには以下の特徴があります：

- Node.js v0.7.6 で導入され、現在のバージョン（例：v22 LTS）でも高い互換性があります。
- ナノ秒 (10^-9) レベルの精度を提供します。
- クロックドリフトの影響を受けません。

`process.hrtime` は、特に時間間隔の測定に特化して設計されています。

**注意**: ブラウザ環境では `hrtime` はサポートされていません。そのため、ブラウザで実現できる最高の精度は `performance.now` を使用したマイクロ秒レベルです（ブラウザごとの実装差異があります）。

以下のように、`process.hrtime` を使用する際は、最初に取得した値を次回の呼び出し時に入力として渡す必要があります：

```js
const NS_PER_SEC = 1e9;
const time = process.hrtime(); // 初回呼び出しで基準となる `time` を取得
// [ 1800216, 25 ]

setTimeout(() => {
  const diff = process.hrtime(time); // 初回取得値を引数に渡し、差分を計算
  // [ 1, 552 ]

  console.log(`計測時間は ${diff[0] * NS_PER_SEC + diff[1]} ナノ秒でした`);
  // 計測時間は 1000000552 ナノ秒でした
}, 1000);
```

## 補足情報

Node.js v10.4 以降では、`process.hrtime.bigint` メソッドも使用できます。これは BigInt ベースの `process.hrtime` のバージョンで、現在の高精度の実時間を返します。

このメソッドは、追加の引数が不要なため、`process.hrtime` よりも使いやすくなっています。以下のように、単に 2 回の呼び出し結果を引き算するだけで、時間差を計算できます：

```js
const start = process.hrtime.bigint();
// 191051479007711n

setTimeout(() => {
  const end = process.hrtime.bigint();
  // 191052633396993n

  console.log(`計測時間は ${end - start} ナノ秒でした`);
  // 計測時間は 1154389282 ナノ秒でした
}, 1000);
```

---

### [私たちはLeapcell、Node.jsプロジェクトのクラウドデプロイの最適解です。](https://leapcell.io/?lc_t=q_hrtime)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/f809178f-9433-2a4b-0813-e8703a8b4c17.png)](https://leapcell.io/?lc_t=q_hrtime)

[Leapcell](https://leapcell.io/?lc_t=q_hrtime)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/dae831dd-ca7d-f382-1917-37dcb165f861.png)](https://leapcell.io/?lc_t=q_hrtime1)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)