← Blog

Turbolog: eliminare il lampeggiamento

Svuotare la console prima di ogni frame la lasciava visibilmente vuota nel 70–81% dei ridisegni di DevTools. Accodare i frame ha ridotto il valore all’1%, interamente sul primo frame.

Adam

Ogni frame di Text.js è una chiamata a console.log. Fino alla versione 0.4, ogni frame era anche preceduto da console.clear(). Sotto carico, la console lampeggiava mostrando il vuoto tra un frame e l’altro.

Misurare il lampeggio

Per misurarlo, abbiamo creato bench/devtools. Avvia Chrome for Testing, apre il vero frontend di DevTools come pagina e lo strumenta:

  • A ogni requestAnimationFrame nel frontend di DevTools, controlla se nel viewport della console è presente un frame completo di 32 righe. Le ridipinture che ne sono prive sono considerate vuote.
  • Decodifica poi i frame di Page.startScreencast per confermare il risultato in pixel.
  • Ogni frame riporta FRAME:<id> · T:<epochMs> nella riga 0, così il frontend può determinare quale frame sta mostrando e con quanto ritardo.

Con console.clear() prima di ogni frame, una dashboard 100×32 a 15 fps risultava vuota nel 70,6% delle ridipinture di DevTools. A 60 fps, nell’81,1%.

Il motivo

ConsoleView di DevTools gestisce le due chiamate in modo diverso:

  1. console.clear() chiama consoleCleared(), che invalida il viewport in modo sincrono. Ogni messaggio scompare nello stesso task.
  2. console.log() chiama addConsoleMessage(), che si limita a pianificare un aggiornamento tramite viewportThrottler, un Throttler con un timeout di 50 ms.

Quindi ogni frame rimuove subito il vecchio frame e disegna quello nuovo più tardi. A 15 fps, per circa 50 ms ogni 66 ms non si vede nulla.

Accodare i frame

Dalla versione 0.4, Turbolog accoda i frame. DevTools resta in fondo alla console, quindi il nuovo frame sostituisce quello vecchio in una sola ridipintura. Uno span iniziale di larghezza zero con margin-top:100vh (la tenda) spinge i frame precedenti fuori dalla vista e termina la propria riga, così il link al sorgente di DevTools non restringe mai la riga 0.

Prima o poi la console deve comunque essere svuotata. Lo svuotamento avviene solo al primo frame, dopo invalidate(), durante l’inattività una volta accumulati 100 frame e al limite massimo di 300.

Adeguare il ritmo al throttler

Inviare frame più velocemente di quanto DevTools riesca a disegnarli non accelera nulla. Un log che arriva mentre il throttler è in attesa lo riavvia, quindi alcuni frame vengono scartati: a 60 fps DevTools ha disegnato 67 dei 295 frame emessi. Turbolog distanzia i frame di almeno 66 ms, circa 15 fps, e continua a renderizzare gli input discreti in modo sincrono quando l’intervallo lo consente.

Interrompere l’aggancio di fase

Gli svuotamenti aggiungevano anche circa 50 ms di ritardo a ogni frame successivo. Dopo uno svuotamento, DevTools inserisce una riga sintetica Console was cleared. Questa occupa lo slot immediato del throttler e sposta il frame successivo sul timer da 50 ms, e con una cadenza costante di 66 ms ogni frame successivo resta agganciato a quel timer.

Il renderer ora attende 110 ms una sola volta dopo ogni svuotamento. Da quel momento, ogni frame arriva più di 50 ms dopo il completamento dell’aggiornamento precedente e segue il percorso immediato di DevTools. Il ritardo mediano di visualizzazione è passato da 63 ms a 10,4 ms.

Risultati

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

DriverFPSRidipinture vuoteDisegnati / emessiRitardo p50
Svuotamento a ogni frame1570,6%70 / 7560,4 ms
Accodamento (1.0)151,0%74 / 7510,4 ms
Svuotamento a ogni frame6081,1%45 / 3009,8 ms
Accodamento, senza cadenza600,8%67 / 29531,2 ms

Il restante 1% è il primo frame, che svuota la console di proposito. Le sue ridipinture vuote sono la riga Console was cleared, non una console vuota.

Meno proprietà per span

Il sorgente di DevTools ha rivelato un altro costo. Copia ogni proprietà estesa di uno stile %c su ogni contenitore di riga, quindi le proprietà abbreviate font: e margin: si espandevano in circa 33 proprietà per span. Ora gli span usano solo proprietà estese, circa 7 ciascuno, con colori esadecimali.

La modalità di accodamento è stata introdotta in Text.js 0.4 e la correzione dell’aggancio di fase arriva nella versione 1.0. Nessuna delle due richiede modifiche al codice dell’applicazione.