Ottimizzare un'app Go

In questo tutorial, eseguirai il deployment di un'applicazione Go intenzionalmente inefficiente, configurata per raccogliere i dati del profilo. Puoi utilizzare l'interfaccia Profiler per visualizzare i dati del profilo e identificare potenziali ottimizzazioni. Puoi quindi modificare l'applicazione, eseguirne il deployment e valutare l'effetto della modifica.

Prima di iniziare

  1. Accedi al tuo account Google Cloud. Se non conosci Google Cloud, crea un account per valutare le prestazioni dei nostri prodotti in scenari reali. I nuovi clienti ricevono anche 300 $di crediti gratuiti per l'esecuzione, il test e il deployment dei carichi di lavoro.
  2. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  3. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  4. Nel pannello di navigazione della console Google Cloud, seleziona API e servizi, fai clic su Abilita API e servizi e abilita l'API Cloud Profiler:

    Vai alle impostazioni dell'API Profiler

  5. Se viene visualizzato API abilitata, significa che l'API è già abilitata. In caso contrario, fai clic sul pulsante Attiva.

  6. Per aprire Cloud Shell, nella barra degli strumenti della console Google Cloud, fai clic su Attiva Cloud Shell:

    Attiva Cloud Shell.

    Dopo qualche istante, all'interno della console Google Cloud si apre una sessione di Cloud Shell:

    di Cloud Shell.

Applicazione di esempio

L'obiettivo principale è massimizzare il numero di query al secondo che il server è in grado di elaborare. Un obiettivo secondario è ridurre l'utilizzo di memoria eliminando le allocazioni di memoria non necessarie.

Il server, utilizzando un framework gRPC, riceve una parola o una frase e poi restituisce il numero di volte in cui la parola o la frase compare nelle opere di Shakespeare.

Il numero medio di query al secondo che il server è in grado di gestire è determinato dai test di carico sul server. Per ogni serie di test, viene chiamato un simulatore client, che gli viene chiesto di eseguire 20 query sequenziali. Al termine di un round, vengono visualizzati il numero di query inviate dal simulatore client, il tempo trascorso e il numero medio di query al secondo.

Il codice del server è intenzionalmente inefficiente.

Esecuzione dell'applicazione di esempio

Scarica ed esegui l'applicazione di esempio:

  1. In Cloud Shell, esegui questi comandi:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    cd golang-samples/profiler/shakesapp
    
  2. Esegui l'applicazione con la versione impostata su 1 e il numero di round impostato su 15:

    go run . -version 1 -num_rounds 15
    

    Dopo uno o due minuti, vengono visualizzati i dati del profilo. I dati del profilo sono simili al seguente esempio:

    Grafico a fiamme iniziale per l'utilizzo del tempo di CPU.

    Nello screenshot, osserva che il Tipo di profilo è impostato su CPU time. Questo indica che i dati di utilizzo della CPU vengono visualizzati nel grafico a fiamme.

    Di seguito è mostrato un esempio di output stampato in Cloud Shell:

    $ go run . -version 1 -num_rounds 15
    2020/08/27 17:27:34 Simulating client requests, round 1
    2020/08/27 17:27:34 Stackdriver Profiler Go Agent version: 20200618
    2020/08/27 17:27:34 profiler has started
    2020/08/27 17:27:34 creating a new profile via profiler service
    2020/08/27 17:27:51 Simulated 20 requests in 17.3s, rate of 1.156069 reqs / sec
    2020/08/27 17:27:51 Simulating client requests, round 2
    2020/08/27 17:28:10 Simulated 20 requests in 19.02s, rate of 1.051525 reqs / sec
    2020/08/27 17:28:10 Simulating client requests, round 3
    2020/08/27 17:28:29 Simulated 20 requests in 18.71s, rate of 1.068947 reqs / sec
    ...
    2020/08/27 17:44:32 Simulating client requests, round 14
    2020/08/27 17:46:04 Simulated 20 requests in 1m32.23s, rate of 0.216849 reqs / sec
    2020/08/27 17:46:04 Simulating client requests, round 15
    2020/08/27 17:47:52 Simulated 20 requests in 1m48.03s, rate of 0.185134 reqs / sec
    

    L'output di Cloud Shell mostra il tempo trascorso per ogni iterazione e il tasso medio di richieste. All'avvio dell'applicazione, la voce "Simulated 20 requests in 17.3s, rate of 1,156069 reqs / sec" indica che il server sta eseguendo circa una richiesta al secondo. Nell'ultimo round, la voce "Simulated 20 richieste in 1m48,03s, rate of 0,185134 req/sec" (Frequenza di 0,185134 req/sec) indica che il server sta eseguendo circa una richiesta ogni 5 secondi.

Utilizzo di profili tempo di CPU per massimizzare le query al secondo

Un approccio per massimizzare il numero di query al secondo consiste nell'identificare i metodi ad alta intensità di CPU e ottimizzare le relative implementazioni. In questa sezione, utilizzerai i profili tempo di CPU per identificare un metodo che utilizza molta CPU nel server.

Identificazione dell'utilizzo del tempo di CPU

Il frame principale del grafico a fiamme elenca il tempo di CPU totale utilizzato dall'applicazione nell'intervallo di raccolta di 10 secondi:

Visualizzazione espansa del frame principale di un grafico a fiamme.

In questo esempio, il servizio ha utilizzato 2.37 s. Quando il sistema viene eseguito su un singolo core, un utilizzo del tempo di CPU di 2,37 secondi corrisponde all'utilizzo del 23,7% del core. Per ulteriori informazioni, consulta Tipi di profilazione disponibili.

Modifica dell'applicazione

Valutare la modifica

Per valutare la modifica:

  1. Esegui l'applicazione con la versione impostata su 2:

    go run . -version 2 -num_rounds 40
    

    Una sezione successiva mostra che, con l'ottimizzazione, il tempo necessario per eseguire una singola fase è molto inferiore a quello dell'applicazione non modificata. Per garantire che l'applicazione venga eseguita abbastanza a lungo da raccogliere e caricare i profili, il numero di round aumenta.

  2. Attendi il completamento dell'applicazione, quindi visualizza i dati del profilo per questa versione dell'applicazione:

    • Fai clic su ORA per caricare i dati del profilo più recenti. Per ulteriori informazioni, consulta la sezione Intervallo di tempo.
    • Nel menu Versione, seleziona 2.

Ad esempio, il grafico a fiamme è il seguente:

Grafico a fiamme che mostra l'utilizzo del tempo di CPU della versione 2.

In questa figura, il frame principale mostra il valore 7.8 s. In seguito alla modifica della funzione di corrispondenza delle stringhe, il tempo di CPU utilizzato dall'applicazione è passato da 2,37 a 7,8 secondi oppure l'applicazione è passata dall'utilizzo del 23,7% di un core della CPU al 78% di un core della CPU.

La larghezza del frame è una misura proporzionale dell'utilizzo del tempo di CPU. In questo esempio, la larghezza del frame per GetMatchCount indica che la funzione utilizza circa il 49% di tutto il tempo di CPU utilizzato dall'applicazione. Nel grafico originale a fiamme, questo stesso fotogramma era pari a circa il 72% della larghezza del grafico. Per visualizzare l'utilizzo esatto del tempo di CPU, puoi utilizzare la descrizione comando del frame o utilizzare l'elenco delle funzioni di stato attivo:

Imposta lo stato attivo sull'elenco delle funzioni che mostra l'utilizzo del tempo di CPU della versione 2.

L'output in Cloud Shell mostra che la versione modificata completa circa 5,8 richieste al secondo:

$ go run . -version 2 -num_rounds 40
2020/08/27 18:21:40 Simulating client requests, round 1
2020/08/27 18:21:40 Stackdriver Profiler Go Agent version: 20200618
2020/08/27 18:21:40 profiler has started
2020/08/27 18:21:40 creating a new profile via profiler service
2020/08/27 18:21:44 Simulated 20 requests in 3.67s, rate of 5.449591 reqs / sec
2020/08/27 18:21:44 Simulating client requests, round 2
2020/08/27 18:21:47 Simulated 20 requests in 3.72s, rate of 5.376344 reqs / sec
2020/08/27 18:21:47 Simulating client requests, round 3
2020/08/27 18:21:51 Simulated 20 requests in 3.58s, rate of 5.586592 reqs / sec
...
2020/08/27 18:23:51 Simulating client requests, round 39
2020/08/27 18:23:54 Simulated 20 requests in 3.46s, rate of 5.780347 reqs / sec
2020/08/27 18:23:54 Simulating client requests, round 40
2020/08/27 18:23:58 Simulated 20 requests in 3.4s, rate of 5.882353 reqs / sec

La piccola modifica all'applicazione ha avuto due diversi effetti:

  • Il numero di richieste al secondo è aumentato da meno di 1 al secondo a 5,8 al secondo.

  • Il tempo di CPU per richiesta, calcolato dividendo l'utilizzo della CPU per il numero di richieste al secondo, è diminuito dal 23,7% al 13,4%.

    Tieni presente che il tempo di CPU per richiesta è diminuito anche se l'utilizzo del tempo di CPU è passato da 2,37 secondi, che corrisponde all'utilizzo del 23,7% di un singolo core CPU, a 7,8 secondi o al 78% di un core CPU.

Utilizzo dei profili heap allocati per migliorare l'utilizzo delle risorse

Questa sezione illustra come utilizzare i profili heap e heap allocati per identificare un metodo che richiede un uso intensivo dell'allocazione nell'applicazione:

  • I profili heap mostrano la quantità di memoria allocata nell'heap del programma nel momento in cui il profilo viene raccolto.

  • I profili heap allocati mostrano la quantità totale di memoria allocata nell'heap del programma durante l'intervallo in cui il profilo è stato raccolto. Dividendo questi valori per 10 secondi (l'intervallo di raccolta del profilo), puoi interpretarli come percentuali di allocazione.

Attivazione della raccolta di profili heap

  1. Esegui l'applicazione con la versione impostata su 3 e abilita la raccolta di profili heap e heap allocati.

    go run . -version 3 -num_rounds 40 -heap -heap_alloc
    
  2. Attendi il completamento dell'applicazione, quindi visualizza i dati del profilo per questa versione dell'applicazione:

    • Fai clic su ORA per caricare i dati del profilo più recenti.
    • Nel menu Versione, seleziona 3.
    • Nel menu Tipo di Profiler, seleziona Heap allocato.

    Ad esempio, il grafico a fiamme è il seguente:

    Grafico a fiamme dei profili heap allocati per la versione 3.

Identificazione del tasso di allocazione heap

Il frame principale mostra la quantità totale di heap allocata durante i 10 secondi in cui è stato raccolto un profilo, calcolata in media su tutti i profili. In questo esempio, il frame radice mostra che, in media, sono stati allocati 1,535 GiB di memoria.

Modifica dell'applicazione

Valutare la modifica

Per valutare la modifica:

  1. Esegui l'applicazione con la versione impostata su 4:

    go run . -version 4 -num_rounds 60 -heap -heap_alloc
    
  2. Attendi il completamento dell'applicazione, quindi visualizza i dati del profilo per questa versione dell'applicazione:

    • Fai clic su ORA per caricare i dati del profilo più recenti.
    • Nel menu Versione, seleziona 4.
    • Nel menu Tipo di Profiler, seleziona Heap allocato.
  3. Per quantificare l'effetto della modifica di readFiles sulla percentuale di allocazione heap;heap, confronta i profili heap allocati per la versione 4 con quelli raccolti per la versione 3:

    Confronto dei profili heap allocati tra le versioni 4 e 3.

    La descrizione comando del frame principale mostra che con la versione 4, la quantità media di memoria allocata durante la raccolta dei profili è diminuita di 1, 301 GiB rispetto alla versione 3. La descrizione comando per readFiles.func1 mostra una riduzione di 1,045 GiB:

    Confronto della descrizione comando dei file readfiles per il tipo di profilo heap allocato.

  4. Per quantificare l'effetto sulla garbage collection, configura un confronto dei profili tempo di CPU. Nello screenshot seguente viene applicato un filtro per mostrare gli stack del garbage collector Go runtime.gcBgMarkWorker.*. Lo screenshot mostra che l'utilizzo della CPU per la garbage collection si è ridotto al 4,97% dal 16,8%.

    Confronto dell'utilizzo del tempo di CPU del processo di garbage collection in background dalla versione 4 alla versione 3.

  5. Per determinare se la modifica ha un impatto sul numero di richieste al secondo gestite dall'applicazione, visualizza l'output in Cloud Shell. In questo esempio, la versione 4 completa fino a 15 richieste al secondo, notevolmente superiori alle 5,8 richieste al secondo della versione 3:

    $ go run . -version 4 -num_rounds 60 -heap -heap_alloc
    2020/08/27 21:51:42 Simulating client requests, round 1
    2020/08/27 21:51:42 Stackdriver Profiler Go Agent version: 20200618
    2020/08/27 21:51:42 profiler has started
    2020/08/27 21:51:42 creating a new profile via profiler service
    2020/08/27 21:51:44 Simulated 20 requests in 1.47s, rate of 13.605442 reqs / sec
    2020/08/27 21:51:44 Simulating client requests, round 2
    2020/08/27 21:51:45 Simulated 20 requests in 1.3s, rate of 15.384615 reqs / sec
    2020/08/27 21:51:45 Simulating client requests, round 3
    2020/08/27 21:51:46 Simulated 20 requests in 1.31s, rate of 15.267176 reqs / sec
    ...
    

    L'aumento delle query al secondo gestite dall'applicazione potrebbe essere dovuto a una riduzione del tempo dedicato alla garbage collection.

  • Per comprendere meglio l'effetto della modifica a readFiles, visualizza i profili heap. Un confronto tra i profili heap della versione 4 e quelli della versione 3 mostra che l'utilizzo dell'heap è sceso a 18,47 MiB da 70,95 MiB:

    Confronto dell'utilizzo dell'heap dalla versione 4 alla versione 3.

Riepilogo

In questa guida rapida, sono stati utilizzati il tempo di CPU e i profili heap allocati per identificare potenziali ottimizzazioni di un'applicazione. Gli obiettivi erano massimizzare il numero di richieste al secondo ed eliminare allocazioni non necessarie.

  • Utilizzando i profili del tempo di CPU, è stata identificata una funzione che consuma molta CPU. Dopo aver applicato una semplice modifica, la tasso di richieste del server è aumentata a 5,8 al secondo, passando da circa 1 al secondo.

  • Utilizzando i profili heap allocati, è stato rilevato che la funzione shakesapp/server.go readFiles presenta una tariffa di allocazione elevata. Dopo aver ottimizzato readFiles, la tasso di richieste del server è aumentata a 15 richieste al secondo e la quantità media di memoria allocata durante la raccolta del profilo di 10 secondi è diminuita di 1,301 GiB.

Passaggi successivi

Per informazioni sull'esecuzione dell'agente Cloud Profiler, vedi: