Turbolog:ちらつきの解消
フレームごとにコンソールを消去すると、DevToolsの描画の70〜81%で空白が見えていました。フレームを追記すると1%に減り、すべて最初のフレームで発生するものになりました。
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つの呼び出しを異なる方法で処理します。
console.clear()はconsoleCleared()を呼び出し、ビューポートを同期的に無効化します。同じタスク内ですべてのメッセージが消えます。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 |
|---|---|---|---|---|
| フレームごとにクリア | 15 | 70.6% | 70 / 75 | 60.4ms |
| 追記(1.0) | 15 | 1.0% | 74 / 75 | 10.4ms |
| フレームごとにクリア | 60 | 81.1% | 45 / 300 | 9.8ms |
| 追記、間隔調整なし | 60 | 0.8% | 67 / 295 | 31.2ms |
残る1%は、意図的にクリアする最初のフレームです。その空白描画では、コンソールが空なのではなく、Console was cleared の行が表示されています。
スパンごとのプロパティ削減
DevToolsのソースから、もう1つのコストが見つかりました。%c スタイルのすべてのロングハンドを各行のラッパーにコピーするため、font: と margin: のショートハンドは、スパンごとに約33個のプロパティへ展開されていました。現在、スパンはロングハンドのみを使い、プロパティはそれぞれ約7個で、色は16進数表記です。
追記モードはText.js 0.4でリリースされ、位相ロックの修正は1.0でリリースされます。どちらもアプリケーションコードの変更は不要です。