Prompt
Date.now()でミリ秒単位の時間を取得しても、なんだかその値にはムラがあるよね?
完全にランダムなタイミングでDate.now()を実行しても、取得することができないミリ秒の周期的な穴がある。
あはは、そこに気づくとは!それはまさに現代のブラウザとCPUが抱える 「セキュリティとプライバシーの防壁」 という名のスイスチーズの穴を見つけちゃったね。
結論から言うと、そのムラや周期的な穴はバグではなく、「サイドチャネル攻撃(特に Spectre や Meltdown)」への対策として、ブラウザ側があえて導入した「時間分解能の制限(タイマーの粒度下げ)」 が原因だよ。
なぜ「正確すぎる時間」が危険なのか、その工学的な背景を解剖してみよう。
1. 犯人は「タイマーの粒度(Timer Resolution)」
本来、コンピュータの時計はナノ秒単位の精度を持っているけれど、JavaScript の Date.now() や performance.now() は、意図的にその精度を落とされている(丸められている)んだ。
-
昔:
performance.now()は 5マイクロ秒(0.005ms)単位で取得できた。 -
今: 多くのブラウザで 100マイクロ秒(0.1ms)〜 1ミリ秒単位 に制限されている。さらに、ブラウザによっては数ミリ秒のランダムな「ジッター(揺らぎ)」をわざと加えていることもあるよ。
2. なぜ「正確なミリ秒」が牙を向くのか?
「たかだかミリ秒で何ができるんだ」と思うかもしれないけれど、情報の「一意な漏洩」を防ぐために、この制限は必須なんだ。
-
Spectre 攻撃の恐怖:
CPU の予測実行の仕組みを悪用して、他人のパスワードなどを盗み出す攻撃がある。このとき、メモリへのアクセス速度(ナノ秒単位の差)を極めて正確に測る必要があるんだ。
-
フィンガープリント:
極微細な時間のズレを測ることで、ユーザーの CPU の個体差(個体識別)ができてしまう。これを防ぐために、ブラウザは「わざと時間を曖昧に」しているんだよ。
3. 「取得できない周期的な穴」の正体
キミが感じた「周期的な穴」は、おそらく OS のクロック割り込み(Clock Interrupt) や ブラウザのメインループ(Event Loop) の周期と関係しているね。
-
量子化誤差: タイマーが 1ms 単位でしか更新されない環境で、0.1ms ごとに
Date.now()を叩いても、同じ値が 10回並んだ後に突然 1 増える、という階段状のデータになる。 -
ブラウザのサスペンド: タブがバックグラウンドに回ると、リソース節約のためにタイマーの更新頻度がさらに落とされる(例:1秒に1回など)。これが大きな「穴」として観測されることもあるよ。
4. もっと正確な時間を知るには?
もしキミが「正方形の敷き詰めアニメーションのフレームレートを正確に測りたい」なら、Date.now() は避けるべきだね。
-
performance.now()を使う:Date.now()よりも高精度で、システムの起動時からの経過時間を返す。ただし、これも前述のセキュリティ対策で丸められているけれど、Dateよりは遥かに信頼できる。 -
requestAnimationFrameの引数を使う:ブラウザが画面を更新するタイミングの「一意なタイムスタンプ」を引数として受け取れる。描画ロジックにはこれが一番「正解」に近いよ。
見落とされがちな視点: SharedArrayBuffer の壁
実は、SharedArrayBuffer という機能を使うと、マルチスレッド(Web Workers)間でメモリを共有して「自作の高精度タイマー」が作れてしまうことがあった。
でも、これも Spectre 対策でデフォルトでは無効化されていたり、特定の HTTP ヘッダー(COOP/COEP)を設定しないと使えないようになっているんだ。ブラウザは「時間を測らせない」ことに必死なんだね。
結論
キミが観測した「周期的な穴」は、ブラウザがキミのプライバシーを守るためにわざと作った「情報の断絶」 なんだ。
-
セキュリティ: ナノ秒単位の計測を禁止して攻撃を防ぐ。
-
プライバシー: 個体識別を難しくする。
-
効率: メインループの負荷を下げる。
次は、「performance.now() を使って、ブラウザごとのタイマーの粒度(何マイクロ秒ごとに値が変わるか)を実測して可視化するスクリプト」 を書いて、その「穴」の正体をグラフにしてみる?
Would you like to code a jitter-measuring script to visualize exactly how your browser is rounding the time values?