Prompt
setIntervalは、ひとつひとつで見ると数ms程度の誤差が毎回発生するけど、全体で見ても数ms程度のだよね。
その洞察、非常に鋭いね!まさに 「短期的なゆらぎ(ジッター)」 と 「長期的な正確さ(ドリフト制御)」 の違いを突いているよ。
結論から言うと、「一つ一つの実行タイミングは OS やブラウザの都合で数 ms ズレるけれど、長期的な誤差(累計のズレ)は最小限に抑えられるように設計されている」 というのが正解だ。
なぜ setInterval が「意外と粘り強く」時間を守ろうとするのか、そのメカニズムを解剖してみよう!
1. 「ドリフト(蓄積誤差)」を防ぐ設計
もし setInterval が「前の処理が終わってから ms 待つ」という仕組み(再帰的な setTimeout)だったら、毎回数 ms の遅延が積み重なって、1時間後には数秒のズレになってしまう。これを 「ドリフト」 と呼ぶんだ。
しかし、ブラウザの setInterval は 「理想的なスケジュール表」 を内部で持っている。
-
理想: に実行したい。
-
現実: OS の割り込みや JS メインスレッドの混雑で、 になる。
このとき、ブラウザは「次は に合わせるぞ」と、本来のターゲット時刻を目指して次の発火を調整する んだ。だから、全体で見ると誤差が累積せず、常に数 ms の範囲に収まり続けるんだね。
2. それでも「数 ms」のズレが発生する理由
なぜ「ピッタリ 0ms」にならないのか。そこには計算機の物理的な限界があるよ。
-
OS スケジューラの精度: Windows や Linux のカーネルがスレッドを切り替える最小単位(タイムスライス)は通常 〜 程度。OS 自体が「正確に 測る」のが苦手なんだ。
-
イベントループのブロッキング: JavaScript はシングルスレッドだから、もし
setIntervalの発火タイミングで別の重い計算をしていたら、その計算が終わるまで実行が待たされる。 -
ブラウザの省電力制限: さっきのバックグラウンドタスクの話にも繋がるけれど、タブが非アクティブになると、ブラウザはバッテリーを守るために
setIntervalの精度を意図的に (1秒)単位まで落とす ことがあるよ。
3. 「スキップ」という副作用
setInterval の面白い(そして怖い)特性として、「実行が間に合わなかったら回数を飛ばす」 という挙動がある。
もし処理が重すぎて、次の発火予定時刻になっても前の処理が終わっていない場合、ブラウザは「キュー(待ち行列)」に同じタスクを 2 つ以上溜めないようにする。その結果、「1回分飛ばされた」 ように見えることがあるんだ。
見落とされがちな視点:高精度な計測が必要なときは?
もし君が音ゲー(TUI の Rust プロジェクトとか!)や精密なタイマーを作りたいなら、setInterval だけでは力不足だ。
-
performance.now(): デートオブジェクト(new Date())よりも遥かに高精度な(マイクロ秒単位の)時刻を取得できる。 -
補正ロジック: 「前回の実行から何 ms 過ぎたか」を
performance.now()で毎回計算し、次の待ち時間を動的に微調整する 「自作のドリフト補正」 を入れるのが、プロのゲーム開発者のやり方だね。
結論
setInterval は 「個別のタイミングはズレるけれど、長期的なリズム(テンポ)は守ろうとする」 健気なやつなんだね。
次は、「実際に performance.now() を使って、君のブラウザで setInterval がどれくらい正確に動いているかを計測するテストコード」 を書いてみる?それとも、「Rust で高精度なタイマーを作るための tokio::time::interval の挙動」 について深掘りしてみる?
Would you like me to code a small benchmark script that visualizes the jitter and drift of setInterval in real-time?