Blink si riferisce all'implementazione della piattaforma web di Chromium e comprende tutte le fasi di rendering precedenti al compositing, che culminano nel commit del compositore. Puoi scoprire di più sull'architettura di rendering di Blink in un articolo precedente di questa serie.
Blink è nato come fork di WebKit, che a sua volta è un fork di KHTML, risalente al 1998. Contiene alcuni dei codici più vecchi (e più critici) di Chromium e, nel 2014, aveva già mostrato la sua età. In quell'anno abbiamo intrapreso una serie di progetti ambiziosi sotto il nome di BlinkNG, con l'obiettivo di risolvere le carenze di lunga data nell'organizzazione e nella struttura del codice di Blink. Questo articolo illustra BlinkNG e i suoi progetti costituenti: perché li abbiamo realizzati, cosa hanno realizzato, i principi guida che hanno plasmato il loro design e le opportunità di miglioramenti futuri che offrono.
Rendering pre-NG
La pipeline di rendering all'interno di Blink è sempre stata concettualmente suddivisa in fasi (style, layout, paint e così via), ma le barriere di astrazione erano inaffidabili. In generale, i dati associati al rendering erano costituiti da oggetti mutabili di lunga durata. Questi oggetti potevano essere e venivano modificati in qualsiasi momento e venivano spesso riciclati e riutilizzati da aggiornamenti di rendering successivi. Era impossibile rispondere in modo affidabile a semplici domande come:
- L'output di stile, layout o pittura deve essere aggiornato?
- Quando questi dati avranno il loro valore "definitivo"?
- Quando è consentito modificare questi dati?
- Quando verrà eliminato questo oggetto?
Esistono molti esempi di questo tipo, tra cui:
Style generava ComputedStyle
in base ai stylesheet, ma ComputedStyle
non era immutabile; in alcuni casi veniva modificato dalle fasi successive della pipeline.
Style genera un albero di LayoutObject
, mentre layout annota questi oggetti con informazioni su dimensioni e posizionamento. In alcuni casi, il layout modificava persino la struttura ad albero. Non è stata stabilita una separazione chiara tra gli input e gli output del layout.
Style generava strutture di dati accessorie che determinavano il corso della composizione e queste strutture di dati venivano modificate in situ da ogni fase successiva a style.
A un livello inferiore, i tipi di dati di rendering sono costituiti in gran parte da alberi specializzati (ad esempio l'albero DOM, l'albero di stili, l'albero di layout, l'albero delle proprietà di pittura) e le fasi di rendering sono implementate come esplorazioni di alberi ricorsivi. Idealmente, un percorso dell'albero dovrebbe essere contenuto: durante l'elaborazione di un determinato nodo dell'albero, non dovremmo accedere a nessuna informazione al di fuori del sottoalbero con radice in quel nodo. Ciò non è mai stato vero prima di RenderingNG; le esplorazioni dell'albero accedevano spesso alle informazioni degli antenati del nodo in fase di elaborazione. Ciò rendeva il sistema molto fragile e soggetto a errori. Inoltre, era impossibile iniziare un percorso dall'albero da qualsiasi punto diverso dalla radice.
Infine, erano presenti molti punti di accesso alla pipeline di rendering sparsi nel codice: layout forzati attivati da JavaScript, aggiornamenti parziali attivati durante il caricamento del documento, aggiornamenti forzati per la preparazione al targeting per eventi, aggiornamenti pianificati richiesti dal sistema di visualizzazione e API specializzate esposte solo al codice di test, per citarne alcuni. Nella pipeline di rendering erano presenti anche alcuni percorsi recursivi e reentranti (ovvero il passaggio all'inizio di una fase dal centro di un'altra). Ognuna di queste rampe di accesso aveva un proprio comportamento idiosincratico e, in alcuni casi, l'output del rendering dipendeva dal modo in cui veniva attivato l'aggiornamento del rendering.
Che cosa abbiamo cambiato
BlinkNG è composto da molti sottoprogetti, grandi e piccoli, tutti con l'obiettivo comune di eliminare i deficit di architettura descritti in precedenza. Questi progetti condividono alcuni principi guida progettati per rendere la pipeline di rendering più simile a una pipeline effettiva:
- Punto di contatto uniforme: dobbiamo sempre accedere alla pipeline all'inizio.
- Fasi funzionali: ogni fase deve avere input e output ben definiti e il suo comportamento deve essere funzionale, ovvero deterministico e ripetibile, e gli output devono dipendere solo dagli input definiti.
- Input costanti: gli input di qualsiasi fase devono essere effettivamente costanti durante l'esecuzione della fase.
- Output immutabili: al termine di uno stage, i relativi output devono essere immutabili per il resto dell'aggiornamento del rendering.
- Coerenza dei checkpoint: alla fine di ogni fase, i dati di rendering prodotti fino a quel momento devono essere in uno stato coerente.
- Deduplicazione del lavoro: calcola ogni elemento una sola volta.
Un elenco completo dei sottoprogetti BlinkNG sarebbe una lettura tediosa, ma di seguito sono riportati alcuni di particolare importanza.
Ciclo di vita del documento
La classe DocumentLifecycle tiene traccia dell'avanzamento della pipeline di rendering. Ci consente di eseguire controlli di base che applicano le invariabili elencate in precedenza, ad esempio:
- Se stiamo modificando una proprietà ComputedStyle, il ciclo di vita del documento deve essere
kInStyleRecalc
. - Se lo stato DocumentLifecycle è
kStyleClean
o successivo,NeedsStyleRecalc()
deve restituire false per qualsiasi nodo collegato. - Quando entri nella fase del ciclo di vita Paint, lo stato del ciclo di vita deve essere
kPrePaintClean
.
Durante l'implementazione di BlinkNG, abbiamo eliminato sistematicamente i percorsi di codice che violavano queste invarianti e abbiamo inserito molte altre asserzioni nel codice per assicurarci di non tornare indietro.
Se hai mai esaminato il codice di rendering a basso livello, potresti chiederti: "Come ci sono arrivato?" Come accennato in precedenza, esistono diversi punti di contatto nella pipeline di rendering. In precedenza, erano inclusi percorsi di chiamata ricorsivi e ricorsivi e punti in cui entravamo nella pipeline in una fase intermedia, anziché dall'inizio. Nel corso di BlinkNG, abbiamo analizzato questi percorsi di chiamata e stabilito che erano tutti riconducibili a due scenari di base:
- Tutti i dati di rendering devono essere aggiornati, ad esempio quando vengono generati nuovi pixel per la visualizzazione o viene eseguito un test di hit per il targeting per evento.
- Abbiamo bisogno di un valore aggiornato per una query specifica a cui è possibile rispondere senza aggiornare tutti i dati di rendering. Sono incluse la maggior parte delle query JavaScript, ad esempio
node.offsetTop
.
Ora esistono solo due punti di contatto nella pipeline di rendering, corrispondenti a questi due scenari. I percorsi di codice ricorsivi sono stati rimossi o sottoposti a refactoring e non è più possibile accedere alla pipeline a partire da una fase intermedia. In questo modo è stato possibile capire esattamente quando e come vengono eseguiti gli aggiornamenti di rendering, semplificando notevolmente la comprensione del comportamento del sistema.
Stile, layout e pre-tinta della pipeline
Nel complesso, le fasi di rendering precedenti a paint sono responsabili di quanto segue:
- Eseguire l'algoritmo di cascata di stili per calcolare le proprietà di stile finali per i nodi DOM.
- Generazione dell'albero del layout che rappresenta la gerarchia delle caselle del documento.
- Determina le informazioni sulle dimensioni e sulla posizione di tutte le caselle.
- Arrotondamento o aggancio della geometria dei subpixel ai confini di pixel interi per la pittura.
- Determinazione delle proprietà dei livelli compositi (trasformazioni affine, filtri, opacità o qualsiasi altro elemento che può essere accelerato tramite GPU).
- Determinare quali contenuti sono cambiati dalla fase di visualizzazione precedente e devono essere visualizzati o visualizzati di nuovo (invalidazione della visualizzazione).
Questo elenco non è cambiato, ma prima di BlinkNG gran parte di questo lavoro veniva svolto in modo ad hoc, suddiviso in più fasi di rendering, con molte funzionalità duplicate e inefficienze integrate. Ad esempio, la fase style è sempre stata principalmente responsabile del calcolo delle proprietà di stile finali per i nodi, ma in alcuni casi speciali non abbiamo determinato i valori delle proprietà di stile finali fino al termine della fase style. Non esisteva un punto formale o applicabile nella procedura di rendering in cui potevamo affermare con certezza che le informazioni sugli stili erano complete e immutabili.
Un altro buon esempio di problema precedente a BlinkNG è l'invalidazione della pittura. In precedenza, l'invalidazione della pittura era presente in tutte le fasi di rendering che precedevano la pittura. Quando si modificava il codice di stile o layout, era difficile sapere quali modifiche alla logica di invalidazione della pittura erano necessarie ed era facile commettere un errore che causava bug di sotto- o sovra-invalidazione. Puoi scoprire di più sulle complessità del vecchio sistema di invalidazione della pittura nell'articolo di questa serie dedicato a LayoutNG.
L'allineamento della geometria del layout dei subpixel ai confini dei pixel interi per la pittura è un esempio di dove abbiamo avuto più implementazioni della stessa funzionalità e abbiamo svolto un sacco di lavoro ridondante. Esisteva un percorso di codice di snap dei pixel utilizzato dal sistema di pittura e un percorso di codice completamente separato utilizzato ogni volta che era necessario un calcolo istantaneo una tantum delle coordinate con snap dei pixel al di fuori del codice di pittura. Inutile dire che ogni implementazione aveva i propri bug e i risultati non sempre corrispondevano. Poiché non era presente la memorizzazione nella cache di queste informazioni, a volte il sistema eseguiva ripetutamente lo stesso calcolo, un'altra fonte di stress per il rendimento.
Ecco alcuni progetti significativi che hanno eliminato i deficit architettonici delle fasi di rendering prima della verniciatura.
Project Squad: pipeline della fase di stile
Questo progetto ha affrontato due principali carenze nella fase di stile che ne impedivano la pipeline corretta:
La fase di stile ha due output principali: ComputedStyle
, contenente il risultato dell'esecuzione dell'algoritmo di applicazione CSS in cascata sull'albero DOM; e un albero di LayoutObjects
, che stabilisce l'ordine delle operazioni per la fase di layout. In teoria, l'esecuzione dell'algoritmo di cascata dovrebbe avvenire strettamente prima della generazione dell'albero del layout, ma in precedenza queste due operazioni erano interlacciate. Project Squad è riuscita a suddividere questi due elementi in fasi distinte e sequenziali.
In precedenza, ComputedStyle
non riceveva sempre il valore finale durante il ricomputo degli stili; in alcune situazioni, ComputedStyle
veniva aggiornato in una fase successiva della pipeline. Project Squad ha eseguito il refactoring di questi percorsi di codice in modo che ComputedStyle
non venga mai modificato dopo la fase di stile.
LayoutNG: pipeline della fase di layout
Questo progetto monumentale, uno dei pilastri di RenderingNG, è stato un'intera riscrittura della fase di rendering del layout. Non renderemo giustizia all'intero progetto qui, ma ci sono alcuni aspetti importanti per il progetto BlinkNG complessivo:
- In precedenza, la fase di layout riceveva un albero di
LayoutObject
creato dalla fase di stile e lo annotava con informazioni su dimensioni e posizione. Di conseguenza, non è stata effettuata una separazione chiara degli input dagli output. LayoutNG ha introdotto l'albero dei frammenti, che è l'output principale di sola lettura del layout e funge da input principale per le fasi di rendering successive. - LayoutNG ha introdotto la proprietà di contenimento nel layout: quando calcoliamo le dimensioni e la posizione di un determinato
LayoutObject
, non cerchiamo più al di fuori del sottoalbero con radice in quell'oggetto. Tutte le informazioni necessarie per aggiornare il layout di un determinato oggetto vengono calcolate in anticipo e fornite come input di sola lettura all'algoritmo. - In precedenza, esistevano casi limite in cui l'algoritmo di layout non era strettamente funzionale: il risultato dell'algoritmo dipendeva dall'aggiornamento del layout precedente più recente. LayoutNG ha eliminato questi casi.
La fase di pre-verniciatura
In precedenza, non esisteva una fase di rendering pre-paint formale, ma solo una serie di operazioni post-layout. La fase di pre-disegno è nata dal riconoscimento del fatto che esistevano alcune funzioni correlate che potevano essere implementate al meglio come un attraversamento sistematico dell'albero del layout al termine del layout; in particolare:
- Emissione di invalidazioni della pittura: è molto difficile eseguire correttamente l'invalidazione della pittura durante il layout, quando le informazioni sono incomplete. È molto più facile da eseguire correttamente e può essere molto efficiente se è suddiviso in due processi distinti: durante lo stile e il layout, i contenuti possono essere contrassegnati con un semplice flag booleano come "potrebbe essere necessario annullare l'aggiornamento della pittura". Durante l'esplorazione dell'albero prima della pittura, controlliamo questi indicatori e emettiamo le invalidazioni necessarie.
- Generare alberi di proprietà di pittura: un processo descritto in maggiore dettaglio di seguito.
- Calcolo e registrazione delle posizioni di pittura con snap ai pixel: i risultati registrati possono essere utilizzati dalla fase di pittura e anche da qualsiasi codice a valle che ne abbia bisogno, senza calcoli ridondanti.
Alberi di proprietà: geometria coerente
I alberi di proprietà sono stati introdotti nelle prime fasi di RenderingNG per gestire la complessità dello scorrimento, che sul web ha una struttura diversa da tutti gli altri tipi di effetti visivi. Prima degli alberi di proprietà, il compositore di Chromium utilizzava una singola gerarchia di "livelli" per rappresentare la relazione geometrica dei contenuti compositi, ma questo approccio è crollato rapidamente quando sono diventate evidenti le complessità di funzionalità come position:fixed. La gerarchia dei livelli ha aggiunto ulteriori puntatori non locali che indicano lo "scroll parent" o il "clip parent" di un livello e, in poco tempo, è stato molto difficile comprendere il codice.
I diagrammi di proprietà hanno risolto il problema rappresentando gli aspetti di scorrimento e clip dei contenuti separatamente da tutti gli altri effetti visivi. In questo modo è stato possibile modellare correttamente la vera struttura visiva e di scorrimento dei siti web. A questo punto, "tutto" ciò che dovevamo fare era implementare algoritmi sulle strutture ad albero delle proprietà, come la trasformazione dello spazio sullo schermo dei livelli compositi o la determinazione dei livelli che scorrevano e di quelli che non lo facevano.
Infatti, abbiamo presto notato che nel codice erano presenti molti altri punti in cui erano state sollevate domande geometriche simili. Il post sulle strutture di dati chiave contiene un elenco più completo. Diversi di questi avevano implementazioni duplicate della stessa operazione eseguita dal codice del compositore; tutti avevano un sottoinsieme diverso di bug e nessuno di questi modellava correttamente la struttura del sito web. La soluzione è diventata chiara: centralizzare tutti gli algoritmi di geometria in un unico posto e rifare il refactoring di tutto il codice per utilizzarli.
Questi algoritmi, a loro volta, dipendono tutti dagli alberi di proprietà, motivo per cui gli alberi di proprietà sono una struttura di dati chiave, ovvero utilizzata in tutta la pipeline, di RenderingNG. Per raggiungere questo obiettivo di codice della geometria centralizzato, abbiamo dovuto introdurre il concetto di alberi di proprietà molto prima nella pipeline, in pre-paint, e modificare tutte le API che ora dipendono da queste in modo che richiedano l'esecuzione di pre-paint prima di poter essere eseguite.
Questa storia è un altro aspetto del pattern di refactoring di BlinkNG: identifica i calcoli chiave, esegui il refactoring per evitarne la duplicazione e crea fasi della pipeline ben definite che generano le strutture di dati che li alimentano. Calcoliamo gli alberi delle proprietà esattamente nel momento in cui sono disponibili tutte le informazioni necessarie e ci assicuriamo che non possano cambiare durante l'esecuzione delle fasi di rendering successive.
Composizione dopo la verniciatura: pipeline di verniciatura e composizione
La stratificazione è il processo di individuazione dei contenuti DOM che vanno nel proprio livello composito (che a sua volta rappresenta una texture GPU). Prima di RenderingNG, la stratificazione veniva eseguita prima della pittura, non dopo (vedi qui per la pipeline attuale; nota la modifica dell'ordine). Prima decidevamo quali parti del DOM andavano in quale livello composito e solo dopo disegnavamo gli elenchi di visualizzazione per queste texture. Naturalmente, le decisioni dipendevano da fattori quali gli elementi DOM animati o in scorrimento o con trasformazioni 3D e gli elementi sovrapposti.
Ciò ha causato grossi problemi, perché più o meno richiedeva dipendenze circolari nel codice, un grosso problema per una pipeline di rendering. Vediamo perché con un esempio. Supponiamo di dover annullare la pittura (ovvero di dover ridisegnare l'elenco di visualizzazione e poi rasterizzarlo di nuovo). La necessità di annullare l'aggiornamento potrebbe derivare da una modifica nel DOM o da uno stile o un layout modificato. Ovviamente, vorremmo invalidare solo le parti effettivamente modificate. Ciò significava scoprire quali livelli compositi erano interessati e poi invalidare parte o tutti gli elenchi di visualizzazione per questi livelli.
Ciò significa che l'invalidazione dipendeva da decisioni prese in precedenza in merito a DOM, stile, layout e stratificazione (passato: per il frame visualizzato in precedenza). Tuttavia, la stratificazione attuale dipende anche da tutti questi fattori. Inoltre, poiché non disponevamo di due copie di tutti i dati di stratificazione, era difficile distinguere le decisioni di stratificazione passate da quelle future. Di conseguenza, abbiamo ottenuto molto codice con ragionamenti circolari. A volte questo ha portato a codice illogico o errato, o addirittura a arresti anomali o problemi di sicurezza, se non eravamo molto attenti.
Per gestire questa situazione, abbiamo introdotto fin dall'inizio il concetto di oggetto DisableCompositingQueryAsserts
. Il più delle volte, se il codice tentava di eseguire query sulle decisioni di stratificazione passate, si verificava un errore di asserzione e il browser andava in crash se era in modalità di debug. In questo modo abbiamo evitato di introdurre nuovi bug. In ogni caso in cui il codice avesse legittimamente bisogno di eseguire query sulle decisioni di stratificazione passate, abbiamo inserito del codice per consentirlo allocando un oggetto DisableCompositingQueryAsserts
.
Il nostro piano era, nel tempo, eliminare tutti gli oggetti call site DisableCompositingQueryAssert
e dichiarare il codice sicuro e corretto. Tuttavia, abbiamo scoperto che alcune chiamate erano praticamente impossibili da rimuovere se la stratificazione avveniva prima della pittura. (Siamo riusciti a rimuoverlo solo di recente). Questo è stato il primo motivo scoperto per il progetto Composite After Paint. Abbiamo imparato che, anche se hai una fase della pipeline ben definita per un'operazione, se non è posizionata correttamente nella pipeline, alla fine si bloccherà.
Il secondo motivo del progetto Composite After Paint era il bug del compositing di base. Un modo per esprimere questo bug è che gli elementi DOM non sono una buona rappresentazione 1:1 di uno schema di stratificazione efficiente o completo per i contenuti delle pagine web. Poiché il compositing era precedente alla fase di pittura, dipendeva più o meno intrinsecamente dagli elementi DOM, non dagli elenchi di visualizzazione o dagli alberi di proprietà. Questo è molto simile al motivo per cui abbiamo introdotto gli alberi di proprietà e, come per gli alberi di proprietà, la soluzione si ottiene direttamente se riesci a capire la fase della pipeline corretta, la esegui al momento giusto e fornisci le strutture di dati chiave corrette. Come per gli alberi di proprietà, questa è stata una buona opportunità per garantire che, una volta completata la fase di pittura, l'output sia immutabile per tutte le fasi successive della pipeline.
Vantaggi
Come hai visto, una pipeline di rendering ben definita offre enormi vantaggi a lungo termine. E ce ne sono anche di più di quanto pensi:
- Maggiore affidabilità: è abbastanza semplice. Il codice più pulito con interfacce ben definite e comprensibili è più facile da comprendere, scrivere e testare. In questo modo è più affidabile. Inoltre, rende il codice più sicuro e stabile, con meno arresti anomali e meno bug di uso dopo svuotamento.
- Copertura dei test ampliata: nel corso di BlinkNG, abbiamo aggiunto molti nuovi test alla nostra suite. Sono inclusi i test di unità che forniscono una verifica mirata degli elementi interni, i test di regressione che ci impediscono di reintrodurre vecchi bug che abbiamo corretto (tantissimi!) e molte aggiunte alla suite di test della piattaforma web pubblica e gestita collettivamente, utilizzata da tutti i browser per misurare la conformità agli standard web.
- Più facile da estendere: se un sistema è suddiviso in componenti chiari, non è necessario comprendere altri componenti a nessun livello di dettaglio per fare progressi con quello attuale. In questo modo, è più facile per tutti aggiungere valore al codice di rendering senza dover essere esperti, nonché ragionare sul comportamento dell'intero sistema.
- Rendimento: ottimizzare gli algoritmi scritti in codice spaghetti è già abbastanza difficile, ma è quasi impossibile ottenere risultati ancora più grandi come animazioni e scorrimento con thread universali o processi e thread per l'isolamento del sito senza una pipeline di questo tipo. Il parallelismo può aiutarci a migliorare notevolmente le prestazioni, ma è anche estremamente complicato.
- Rendimento e contenimento: BlinkNG offre diverse nuove funzionalità che consentono di utilizzare la pipeline in modi nuovi e innovativi. Ad esempio, cosa succederebbe se volessimo eseguire la pipeline di rendering solo fino alla scadenza di un budget? Oppure saltare il rendering per gli alberi secondari noti per non essere pertinenti per l'utente in questo momento? È ciò che consente la proprietà CSS content-visibility. E se lo stile di un componente dipendesse dal relativo layout? Si tratta di query sui contenitori.
Case study: query sui contenitori
Le query dei contenitori sono una funzionalità della piattaforma web molto attesa (è la funzionalità più richiesta dagli sviluppatori CSS da anni). Se è così fantastico, perché non esiste ancora? Il motivo è che un'implementazione delle query del contenitore richiede una comprensione e un controllo molto attenti della relazione tra il codice di stile e il codice di layout. Diamo un'occhiata più da vicino.
Una query contenitore consente agli stili applicati a un elemento di dipendere dalle dimensioni di un elemento antecedente. Poiché le dimensioni del layout vengono calcolate durante il layout, significa che dobbiamo eseguire il ricomputo degli stili dopo il layout; ma il ricomputo degli stili viene eseguito prima del layout. Questo paradosso del pollo e dell'uovo è il motivo per cui non è stato possibile implementare le query sui contenitori prima di BlinkNG.
Come possiamo risolvere il problema? Non è una dipendenza dalla pipeline precedente, ovvero lo stesso problema risolto da progetti come Composite After Paint? Peggio ancora, cosa succede se i nuovi stili cambiano le dimensioni dell'antenato? A volte questo non può portare a un loop infinito?
In linea di principio, la dipendenza circolare può essere risolta utilizzando la proprietà CSS contain, che consente di eseguire il rendering all'esterno di un elemento senza che questo dipenda dal rendering all'interno del sottoalbero dell'elemento. Ciò significa che i nuovi stili applicati da un contenitore non possono influire sulle dimensioni del contenitore, perché le query dei contenitori richiedono il contenimento.
Tuttavia, non è stato sufficiente ed è stato necessario introdurre un tipo di contenimento meno restrittivo rispetto al contenimento delle dimensioni. Questo perché è comune volere che un contenitore di query del contenitore possa ridimensionarsi in una sola direzione (di solito in blocco) in base alle sue dimensioni in linea. È stato quindi aggiunto il concetto di contenimento delle dimensioni in linea. Tuttavia, come puoi vedere dalla nota molto lunga in quella sezione, per molto tempo non è stato chiaro se fosse possibile il contenimento delle dimensioni in linea.
Una cosa è descrivere il contenimento in un linguaggio astratto delle specifiche e un'altra è implementarlo correttamente. Ricorda che uno degli obiettivi di BlinkNG era applicare il principio di contenimento alle esplorazioni dell'albero che costituiscono la logica principale del rendering: durante l'esplorazione di un sottoalbero, non devono essere richieste informazioni esterne al sottoalbero. A quanto pare (non è stato esattamente un incidente), è molto più semplice e pulito implementare il contenimento CSS se il codice di rendering rispetta il principio di contenimento.
Futuro: composizione off-main-thread e non solo.
La pipeline di rendering mostrata qui è in realtà un po' più avanti dell'attuale implementazione di RenderingNG. Mostra che la stratificazione non è attiva nel thread principale, mentre al momento è ancora attiva nel thread principale. Tuttavia, è solo questione di tempo prima che ciò avvenga, ora che Composite After Paint è stato rilasciato e la stratificazione avviene dopo la pittura.
Per capire perché è importante e dove potrebbe portare, dobbiamo considerare l'architettura del motore di rendering da un punto di vista leggermente più elevato. Uno degli ostacoli più duraturi al miglioramento delle prestazioni di Chromium è il semplice fatto che il thread principale del renderer gestisce sia la logica di applicazione principale (ovvero lo script in esecuzione) sia la maggior parte del rendering. Di conseguenza, il thread principale è spesso saturo di lavoro e la congestione del thread principale è spesso il collo di bottiglia dell'intero browser.
La buona notizia è che non deve necessariamente essere così. Questo aspetto dell'architettura di Chromium risale ai tempi di KHTML, quando l'esecuzione a thread singolo era il modello di programmazione dominante. Quando i processori multi-core sono diventati comuni nei dispositivi di consumo, l'ipotesi di un solo thread era stata completamente integrata in Blink (in precedenza WebKit). Volevamo introdurre più threading nel motore di rendering da molto tempo, ma era semplicemente impossibile nel vecchio sistema. Uno degli obiettivi principali di Rendering NG era uscire da questo problema e rendere possibile spostare il lavoro di rendering, in parte o del tutto, in un altro thread o in altri thread.
Ora che BlinkNG è in dirittura di arrivo, stiamo già iniziando a esplorare questa area. Il commit non bloccante è un primo approccio alla modifica del modello di threading del renderer. Il commit del compositore (o semplicemente commit) è un passaggio di sincronizzazione tra il thread principale e il thread del compositore. Durante il commit, vengono create copie dei dati di rendering prodotti nel thread principale, da utilizzare dal codice di composizione a valle in esecuzione nel thread del compositore. Durante la sincronizzazione, l'esecuzione del thread principale viene interrotta mentre il codice di copia viene eseguito nel thread del compositore. Questo viene fatto per assicurarsi che il thread principale non modifichi i dati di rendering mentre il thread del compositore li copia.
Il commit non bloccante elimina la necessità di interrompere il thread principale e attendere il termine della fase di commit: il thread principale continuerà a lavorare mentre il commit viene eseguito contemporaneamente nel thread del compositore. L'effetto netto del commit non bloccante sarà una riduzione del tempo dedicato al rendering del lavoro sul thread principale, che diminuirà la congestione sul thread principale e migliorerà le prestazioni. Al momento (marzo 2022), abbiamo un prototipo funzionante di commit non bloccante e ci stiamo preparando a eseguire un'analisi dettagliata del suo impatto sul rendimento.
È in attesa il compositing fuori dal thread principale, con l'obiettivo di far corrispondere il motore di rendering all'illustrazione spostando la gerarchia dei livelli dal thread principale a un thread di lavoro. Come per l'commit non bloccante, questo ridurrà la congestione sul thread principale diminuendo il carico di lavoro di rendering. Un progetto come questo non sarebbe mai stato possibile senza i miglioramenti architettonici di Composite After Paint.
E sono in programma altri progetti (è un gioco di parole). Finalmente abbiamo una base che ci consente di sperimentare la ridistribuzione del lavoro di rendering e non vediamo l'ora di scoprire cosa è possibile fare.