← Blog

Turbolog: eliminowanie migotania

Czyszczenie konsoli przed każdą klatką pozostawiało ją widocznie pustą w 70–81% odmalowań DevTools. Dopisywanie klatek zmniejszyło ten udział do 1%, wyłącznie przy pierwszej klatce.

Adam

Każda klatka Text.js to jedno wywołanie console.log. Do wersji 0.4 każdą klatkę poprzedzało też console.clear(). Pod obciążeniem konsola między klatkami na moment stawała się pusta.

Pomiar migotania

Aby to zmierzyć, stworzyliśmy bench/devtools. Uruchamia Chrome for Testing, otwiera rzeczywisty frontend DevTools jako stronę i dodaje do niego instrumentację:

  • Przy każdym requestAnimationFrame we frontendzie DevTools sprawdza, czy w obszarze widocznym konsoli znajduje się pełna klatka o 32 wierszach. Odmalowania bez niej to puste odmalowania.
  • Następnie dekoduje klatki Page.startScreencast, aby potwierdzić wynik na poziomie pikseli.
  • Każda klatka zawiera FRAME:<id> · T:<epochMs> w wierszu 0, dzięki czemu frontend rozpoznaje wyświetlaną klatkę i jej opóźnienie.

Przy console.clear() przed każdą klatką dashboard 100×32 przy 15 fps był pusty w 70,6% odmalowań DevTools. Przy 60 fps było to 81,1%.

Przyczyna

Komponent ConsoleView w DevTools obsługuje te dwa wywołania inaczej:

  1. console.clear() wywołuje consoleCleared(), które synchronicznie unieważnia obszar widoczny. Wszystkie komunikaty znikają w tym samym zadaniu.
  2. console.log() wywołuje addConsoleMessage(), które jedynie planuje odświeżenie przez viewportThrottler, instancję Throttler z limitem czasu 50 ms.

Każda klatka usuwa więc starą klatkę od razu, a nową odmalowuje później. Przy 15 fps przez około 50 z każdych 66 ms nic nie widać.

Dopisywanie klatek

Od wersji 0.4 Turbolog dopisuje klatki. DevTools utrzymuje widok przy dole konsoli, więc nowa klatka zastępuje starą w jednym odmalowaniu. Początkowy span o zerowej szerokości z margin-top:100vh (kurtyna) wypycha wcześniejsze klatki poza widok i kończy własny wiersz, dzięki czemu link do źródła w DevTools nigdy nie zwęża wiersza 0.

Konsolę nadal trzeba w końcu opróżnić. Czyszczenie następuje tylko przy pierwszej klatce, po invalidate(), w bezczynności po zgromadzeniu 100 klatek oraz po osiągnięciu sztywnego limitu 300.

Dostosowanie tempa do mechanizmu ograniczającego

Wysyłanie klatek szybciej, niż DevTools może je odmalować, niczego nie przyspiesza. Wpis, który dociera, gdy mechanizm ograniczający czeka, uruchamia go ponownie, więc klatki są pomijane: przy 60 fps DevTools odmalowało 67 z 295 wysłanych klatek. Turbolog zachowuje odstępy co najmniej 66 ms między klatkami, czyli około 15 fps, i nadal renderuje pojedyncze zdarzenia wejściowe synchronicznie, gdy odstęp na to pozwala.

Usuwanie blokady fazowej

Czyszczenie dodawało też około 50 ms opóźnienia do każdej kolejnej klatki. Po wyczyszczeniu DevTools wstawia syntetyczny wiersz Console was cleared. Zajmuje on miejsce na natychmiastowe wykonanie w mechanizmie ograniczającym i przesuwa następną klatkę na timer 50 ms, a przy stałym rytmie 66 ms każda kolejna klatka pozostaje zsynchronizowana z tym timerem.

Renderer czeka teraz jednorazowo 110 ms po każdym wyczyszczeniu. Następnie każda klatka dociera ponad 50 ms po zakończeniu poprzedniego odświeżenia i korzysta z natychmiastowej ścieżki DevTools. Mediana opóźnienia wyświetlania spadła z 63 ms do 10,4 ms.

Wyniki

bun bench/devtools/run.ts --strategy=native,clear --fps=15,60 --seconds=5, dashboard 100×32, rzeczywisty frontend DevTools:

TrybFPSPuste odmalowaniaOdmalowane / wysłaneOpóźnienie p50
Czyszczenie co klatkę1570,6%70 / 7560,4 ms
Dopisywanie (1.0)151,0%74 / 7510,4 ms
Czyszczenie co klatkę6081,1%45 / 3009,8 ms
Dopisywanie bez regulacji tempa600,8%67 / 29531,2 ms

Pozostały 1% przypada na pierwszą klatkę, która celowo czyści konsolę. Jej puste odmalowania to wiersz Console was cleared, a nie pusta konsola.

Mniej właściwości na span

Kod źródłowy DevTools ujawnił jeszcze jeden koszt. Kopiuje każdą właściwość składową stylu %c do każdego elementu opakowującego wiersz, więc skrócone zapisy font: i margin: rozwijały się do około 33 właściwości na span. Spany używają teraz wyłącznie właściwości składowych, około 7 na każdy, z kolorami w zapisie szesnastkowym.

Tryb dopisywania pojawił się w Text.js 0.4, a poprawka blokady fazowej trafia do wersji 1.0. Żadna z tych zmian nie wymaga zmian w kodzie aplikacji.