← Tutti gli articoli

La PGO del server di database riorganizza il codice attorno ai percorsi di esecuzione più utilizzati

La PGO del server di database riorganizza il codice attorno ai percorsi di esecuzione più utilizzati

La PGO, ovvero l’ottimizzazione guidata dal profilo, del database relazionale open source usa le informazioni raccolte durante un’esecuzione di addestramento per modificare il modo in cui il software del database viene compilato in istruzioni macchina eseguibili. Nella sua spiegazione del processo, Percona descrive una tecnica che non si limita ad accelerare le query usate per l’addestramento: riorganizza il codice nell’intero programma del database. La corrispondenza importante con l’ambiente di produzione riguarda quali percorsi di esecuzione vengono eseguiti frequentemente, non la presenza degli stessi valori nei dati di addestramento.

La spiegazione di Percona costituisce la seconda parte di una serie in tre parti, tra un’introduzione alla PGO e un approfondimento sui motivi per cui una build addestrata con sysbench, uno strumento per testare i carichi di lavoro, perde prestazioni. Questa puntata illustra i meccanismi anziché presentare risultati comparativi di benchmark. Non fornisce quindi alcun aumento di velocità misurato che chi gestisce un database possa presumere valido per la propria installazione.

Due build, con un’esecuzione di addestramento tra le due

La PGO inizia con la compilazione di una build strumentata, una versione del programma con contatori aggiuntivi che registrano l’attività di esecuzione. Percona descrive l’uso dell’opzione del compilatore per la generazione del profilo in questa fase, l’esecuzione di un carico di lavoro di addestramento e la scrittura delle misurazioni in file di profilo grezzi. Una seconda compilazione usa l’attività registrata per scegliere come organizzare e ottimizzare il programma finale. I dettagli esatti della profilazione dipendono dal compilatore, lo strumento che traduce il codice sorgente in istruzioni macchina.

Le informazioni utili sono più dettagliate di un semplice conteggio delle chiamate a funzione, dove una funzione è una porzione di codice riutilizzabile. Percona descrive contatori sui collegamenti del grafo del flusso di controllo, la mappa dei possibili percorsi attraverso un programma. Questi registrano le scelte tra diramazioni, i passaggi ripetuti nei cicli e l’attività nei punti di chiamata, cioè i punti in cui una funzione ne richiama un’altra. Il suo esempio illustrativo distingue una diramazione percorsa 950.000 volte da un’alternativa percorsa soltanto 50 volte. Si tratta di conteggi esplicativi, non di risultati di benchmark del database.

Inlining: usare spazio per il codice dove serve

L’inlining consiste nell’inserire le istruzioni di una funzione direttamente nel punto in cui viene chiamata, invece di effettuare una chiamata separata. In assenza di informazioni raccolte durante l’esecuzione, i compilatori si basano su criteri come le dimensioni della funzione, il numero di punti di chiamata e le stime di costi e benefici. I profili possono giustificare l’inlining di una chiamata eseguita milioni di volte anche quando i normali criteri dimensionali lo escluderebbero. Viceversa, una chiamata in codice eseguito raramente può rimanere separata, perché copiarne le istruzioni aumenterebbe le dimensioni del programma con pochi vantaggi.

La disposizione delle diramazioni modifica quale percorso prosegue direttamente nelle istruzioni successive e quale richiede un salto altrove. I processori prevedono l’esito delle diramazioni per recuperare le istruzioni prima dell’esecuzione. Una previsione errata li costringe a scartare il lavoro speculativo, causando un breve stallo. Far proseguire senza salti il percorso più comune può agevolare il recupero delle istruzioni, anche se i costi effettivi dipendono dal processore e dalla correttezza della previsione. Percona descrive lo spostamento della gestione degli errori rari lontano da quel percorso, spesso nella sezione del codice improbabile, una sezione dell’eseguibile riservata alle istruzioni usate di rado.

Cambiare l’ordine di un’istruzione if-else nel codice sorgente non è un’ottimizzazione equivalente. Il compilatore sceglie la disposizione del codice macchina usando i profili o i propri criteri; l’ordine in cui lo sviluppatore scrive le alternative generalmente influisce poco o per nulla sulla disposizione finale.

Tenere vicine le istruzioni usate di frequente

La suddivisione delle funzioni in parti hot e cold separa, all’interno della stessa funzione, le istruzioni eseguite di frequente, o hot, da quelle eseguite raramente, o cold. Un ciclo molto utilizzato può rimanere nella sezione hot, riservata al codice usato di frequente, mentre la sua parte finale dedicata alla gestione degli errori insoliti viene spostata nella sezione del codice improbabile. Un salto continua a collegare le parti quando necessario. La porzione hot, più piccola, sfrutta meglio la cache delle istruzioni, la memoria piccola e veloce del processore che conserva le istruzioni da eseguire a breve, invece di condividere quello spazio con codice usato di rado.

Il riordinamento riguarda anche intere funzioni quando l’eseguibile viene assemblato in fase di linking. Percona afferma che la build PGO del server di database abilita la LTO, l’ottimizzazione in fase di linking, che permette alla catena di strumenti di ottimizzare il codice attraverso parti del programma compilate separatamente. Le funzioni che si chiamano spesso a vicenda o vengono eseguite in successione possono essere collocate vicine. Questo migliora la località degli accessi alla cache delle istruzioni e all’iTLB: l’instruction translation lookaside buffer è una cache del processore che aiuta a individuare la memoria contenente le istruzioni.

Percona descrive questa riorganizzazione dell’intero programma come una delle fonti potenzialmente più importanti di miglioramento per l’eseguibile del server di database, parlando di spostamenti all’interno di un file binario di oltre 100 MB. Il normale ordine di costruzione della build non rispecchia necessariamente i percorsi usati durante l’esecuzione. Raggruppare le funzioni hot correlate riduce la necessità di recuperare istruzioni da posizioni molto distanti, ma l’articolo non quantifica questo vantaggio per una specifica installazione.

Chiamate più prevedibili e scelte diverse sulle dimensioni del codice

La promozione delle chiamate indirette riguarda le chiamate la cui destinazione viene selezionata durante l’esecuzione del programma, come le chiamate tramite puntatori a funzione o le chiamate virtuali di C++. Le chiamate virtuali usano comunemente le vtable, tabelle che selezionano l’implementazione di un’operazione per un oggetto. Percona cita un’interfaccia per i motori di archiviazione in cui, di fatto, è registrato soltanto il motore di archiviazione dei dati del database come esempio di situazione in cui una destinazione può prevalere. Il compilatore può inserire un controllo: chiamare direttamente quella destinazione frequente quando corrisponde, altrimenti mantenere la chiamata indiretta originale. In questo modo si conserva il percorso alternativo, rendendo al tempo stesso più facile prevedere la destinazione più comune.

I profili influenzano anche l’allocazione dei registri, cioè l’assegnazione dei valori alla memoria di lavoro più veloce del processore, e lo srotolamento dei cicli, che ripete le istruzioni di un ciclo per ridurre il costo delle operazioni di controllo del ciclo stesso. I percorsi hot possono privilegiare la velocità, con uno srotolamento più aggressivo e più registri dedicati ai valori richiesti frequentemente. I percorsi cold possono privilegiare la compattezza. In questa discussione Percona menziona l’opzione del compilatore per l’addestramento con profilo parziale; il trattamento del codice non coperto dall’addestramento dipende dalle impostazioni del compilatore. Le istruzioni usate raramente occupano comunque spazio su disco e nella cache delle pagine, la memoria che conserva i contenuti dei file, quindi le loro dimensioni e la loro collocazione contano anche quando vengono eseguite di rado.

Un’istruzione switch, che seleziona tra più alternative, presenta un altro compromesso. Un compilatore può usare una tabella dei salti, una struttura di consultazione che offre una selezione di ordine uno, cioè a tempo costante, ma richiede una consultazione della tabella e un salto indiretto. In alternativa, può generare confronti successivi. Le informazioni del profilo possono rendere convenienti i confronti quando un caso prevale ed è prevedibile, anziché trattare ogni caso come ugualmente probabile.

Disponibile anche in: EnglishDeutsch