← ブログ

Turbolog:ちらつきの解消

フレームごとにコンソールを消去すると、DevToolsの描画の70〜81%で空白が見えていました。フレームを追記すると1%に減り、すべて最初のフレームで発生するものになりました。

Adam

Text.jsの各フレームは、1回の console.log 呼び出しです。0.4までは、各フレームの前に console.clear() も呼び出していました。負荷がかかると、フレーム間でコンソールが一瞬空になっていました。

ちらつきの測定

これを測定するために、bench/devtools を作成しました。Chrome for Testingを起動し、実際のDevToolsフロントエンドをページとして開いて、計測処理を組み込みます。

  • DevToolsフロントエンドの requestAnimationFrame ごとに、32行の完全なフレームがコンソールのビューポートにあるかを確認します。完全なフレームがない描画を空白描画とします。
  • 後から Page.startScreencast のフレームをデコードし、ピクセル単位で結果を確認します。
  • 各フレームの行0には FRAME:<id> · T:<epochMs> が含まれるため、フロントエンドは表示中のフレームとその遅延を判別できます。

各フレームの前に console.clear() を呼び出すと、15fpsの100×32ダッシュボードでは、DevToolsの描画の 70.6% が空白でした。60fpsでは 81.1% でした。

原因

DevToolsの ConsoleView は、2つの呼び出しを異なる方法で処理します。

  1. console.clear() は consoleCleared() を呼び出し、ビューポートを同期的に無効化します。同じタスク内ですべてのメッセージが消えます。
  2. console.log() は addConsoleMessage() を呼び出し、タイムアウトが 50ms の Throttler である viewportThrottler を通じて、更新を予約するだけです。

そのため、各フレームは古いフレームを即座に削除し、新しいフレームを後から描画します。15fpsでは、66msごとにおよそ50msは何も表示されません。

フレームの追記

0.4以降、Turbologはフレームを追記します。DevToolsはコンソールの最下部を表示し続けるため、1回の描画内で新しいフレームが古いフレームを置き換えます。先頭に置いた margin-top:100vh を持つ幅ゼロのスパン(カーテン)が、以前のフレームを表示領域の外に押し出し、そのスパン自体の行をそこで終えるため、DevToolsのソースリンクが行0の幅を狭めることはありません。

それでも、いずれはコンソールを空にする必要があります。クリアするのは、最初のフレーム、invalidate() の後、100フレームが積み重なった後のアイドル時、そして上限の300に達したときだけです。

スロットラーに合わせた送信間隔

DevToolsが描画できる速度を超えてフレームを送っても、速くはなりません。スロットラーの待機中にログが届くとスロットラーが再起動するため、フレームが間引かれます。60fpsでは、送信した295フレームのうちDevToolsが描画したのは67フレームでした。Turbologはフレームの間隔を最低 66ms、およそ15fpsに調整しつつ、その間隔が許す場合は個別の入力を同期的にレンダリングします。

位相ロックの解消

クリアは、それ以降のすべてのフレームに約50msの遅延も加えていました。クリア後、DevToolsは Console was cleared という合成行を挿入します。この行がスロットラーの即時実行枠を使い、次のフレームを50msのタイマーに回すため、66msの一定間隔では、それ以降のすべてのフレームがそのタイマーに固定されたままになります。

現在のレンダラーは、クリア後に1回だけ 110ms 待機します。その後は、各フレームが前の更新完了から50msを超えて到着し、DevToolsの即時処理経路に入ります。表示遅延の中央値は 63msから10.4ms になりました。

結果

bun bench/devtools/run.ts --strategy=native,clear --fps=15,60 --seconds=5、100×32ダッシュボード、実際のDevToolsフロントエンド。

方式FPS空白描画描画数 / 送信数遅延p50
フレームごとにクリア1570.6%70 / 7560.4ms
追記(1.0)151.0%74 / 7510.4ms
フレームごとにクリア6081.1%45 / 3009.8ms
追記、間隔調整なし600.8%67 / 29531.2ms

残る1%は、意図的にクリアする最初のフレームです。その空白描画では、コンソールが空なのではなく、Console was cleared の行が表示されています。

スパンごとのプロパティ削減

DevToolsのソースから、もう1つのコストが見つかりました。%c スタイルのすべてのロングハンドを各行のラッパーにコピーするため、font: と margin: のショートハンドは、スパンごとに約33個のプロパティへ展開されていました。現在、スパンはロングハンドのみを使い、プロパティはそれぞれ約7個で、色は16進数表記です。

追記モードはText.js 0.4でリリースされ、位相ロックの修正は1.0でリリースされます。どちらもアプリケーションコードの変更は不要です。