Otimizar um app Go
Neste guia de início rápido, você implanta um aplicativo Go intencionalmente ineficiente configurado para coletar dados de perfis. Você usa a interface do Profiler para visualizar os dados do perfil e identificar possíveis otimizações. Em seguida, você modifica o aplicativo, o implanta e avalia o efeito da modificação.
Antes de começar
- Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Enable the required API.
- Para abrir o Cloud Shell, na barra de ferramentas do Console do Google Cloud, clique
em Ativar o Cloud Shell:
Após alguns instantes, uma sessão do Cloud Shell é aberta no Console do Google Cloud:
Exemplo de aplicativo
O objetivo principal é maximizar o número de consultas por segundo que o servidor pode processar. O objetivo secundário é reduzir o uso da memória ao eliminar alocações de memória desnecessárias.
O servidor, usando um framework gRPC, recebe uma palavra ou frase e retorna o número de vezes que a palavra ou frase aparece nas obras de Shakespeare.
O número médio de consultas por segundo que o servidor pode processar é determinado pelo teste de carga do servidor. Para cada rodada de testes, um simulador de cliente é chamado e instruído a emitir 20 consultas sequenciais. Na conclusão de uma rodada, são exibidos o número de consultas enviadas pelo simulador de cliente, o tempo decorrido e o número médio de consultas por segundo.
O código do servidor é intencionalmente ineficiente.
Como executar o aplicativo de amostra
Faça o download e execute o aplicativo de amostra:
No Cloud Shell, execute os seguintes comandos:
git clone https://github.com/GoogleCloudPlatform/golang-samples.git cd golang-samples/profiler/shakesapp
Execute o aplicativo com a versão definida como
1
e o número de rodadas definido como 15:go run . -version 1 -num_rounds 15
Depois de um ou dois minutos, os dados do perfil são exibidos. Os dados do perfil são semelhantes ao exemplo a seguir:
Na captura de tela, observe que o Tipo de perfil está definido como
CPU time
. Isso indica que os dados de uso da CPU são exibidos no gráfico em degradê.O resultado da amostra exibido no Cloud Shell é mostrado abaixo:
$ 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
A resposta do Cloud Shell exibe o tempo decorrido de cada iteração e a taxa média de solicitações. Quando o aplicativo é iniciado, a entrada "Simulou 20 solicitações em 17,3s, taxa de 1,156069 solicitações / s" indica que o servidor está executando cerca de uma solicitação por segundo. Na última rodada, a entrada "Simulou 20 solicitações em 1m48.03s, taxa de 0,185134 solicitações / s" indica que o servidor está executando cerca de uma solicitação a cada cinco segundos.
Como usar perfis de tempo de CPU para maximizar consultas por segundo
Uma abordagem para maximizar o número de consultas por segundo é identificar métodos com uso intensivo da CPU e otimizar suas implementações. Nesta seção, você usa perfis de tempo de CPU para identificar um método que consome muita CPU no servidor.
Como identificar o uso do tempo de CPU
O frame raiz do gráfico em degradê lista o tempo total de CPU usado pelo aplicativo durante o intervalo de coleta de 10 segundos:
Neste exemplo, o serviço usou 2.37 s
. Quando o sistema é executado em um único
núcleo, o uso de tempo de CPU de 2,37 segundos corresponde a 23,7% de utilização desse
núcleo. Saiba mais em
Tipos de criação de perfil disponíveis.
Como modificar o aplicativo
Etapa 1: qual função consome muito tempo da CPU?
Uma maneira de identificar o código que talvez precise ser otimizado é visualizar a tabela de funções e identificar funções gulosas:
- Para visualizar a tabela, clique em list Lista de funções de foco.
- Classifique a tabela por Total. A coluna
Total mostra o uso do tempo de CPU de uma função e dos filhos dela.
Neste exemplo,
GetMatchCount
é a primeira funçãoshakesapp/server.go
listada. Essa função usou 1,7 segundo do tempo de CPU total, ou 72% do tempo de CPU total em aplicativos. Essa função é conhecida por processar as solicitações gRPC.O gráfico de chama mostra que a função
shakesapp/server.go
GetMatchCount
chamaMatchString
, que, por sua vez, está gastando a maior parte do tempo chamandoCompile
:
Etapa 2: como você pode usar o que aprendeu?
- Conte com sua experiência em linguagens.
MatchString
é um método de expressão regular. Você sabe que o processamento de expressões regulares é muito flexível, mas não necessariamente a solução mais eficiente para cada problema. - Conte com sua experiência em aplicativos. O cliente está gerando uma palavra ou frase, e o servidor está pesquisando essa frase.
- Pesquise a implementação do
método
shakesapp/server.go
GetMatchCount
para usos deMatchString
e, em seguida, avalie se uma função mais simples e eficiente pode substituir essa chamada.
Etapa 3: como o aplicativo pode ser alterado?
No arquivo shakesapp/server.go
, o
código atual contém uma chamada para MatchString
:
isMatch, err := regexp.MatchString(query, line) if err != nil { return resp, err } if isMatch { resp.MatchCount++ }
Uma opção é substituir a lógica MatchString
por uma lógica equivalente que use strings.Contains
:
if strings.Contains(line, query) { resp.MatchCount++ }
Remova a declaração de importação do pacote regexp
.
Como avaliar a alteração
Para avaliar a alteração, faça o seguinte:
Execute o aplicativo com a versão definida como
2
:go run . -version 2 -num_rounds 40
Uma seção posterior mostra que, com a otimização, o tempo necessário para executar uma única rodada é muito menor do que o do aplicativo não modificado. Para garantir a execução do aplicativo por um tempo suficiente para coletar e fazer o upload de perfis, o número de rodadas será maior.
Aguarde a conclusão do aplicativo e, em seguida, visualize os dados do perfil para essa versão do aplicativo:
- Clique em AGORA para carregar os dados mais recentes do perfil. Saiba mais em Intervalo de tempo.
- No menu Versão, selecione 2.
Por exemplo, o gráfico de chama é:
Nessa figura, o frame principal mostra um valor de 7.8 s
. Como resultado da
alteração da função de correspondência de strings, o tempo de CPU usado pelo aplicativo
aumentou de 2,37 segundos para 7,8 segundos, ou, o aplicativo passou do uso
de 23,7% para 78% de um núcleo da CPU.
A largura do frame é uma medida proporcional do uso do tempo de CPU. Neste
exemplo, a largura do frame para GetMatchCount
indica que a função
usa cerca de 49% de todo o tempo de CPU usado pelo aplicativo.
No gráfico de chama original,
esse mesmo frame tinha cerca de 72% da largura do gráfico.
Para ver o uso exato de tempo de CPU, use a dica do frame ou
a lista de funções de foco:
A saída no Cloud Shell mostra que a versão modificada está concluindo cerca de 5,8 solicitações por segundo:
$ 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
A pequena alteração no aplicativo teve dois efeitos diferentes:
O número de solicitações por segundo aumentou de menos de 1 por segundo para 5,8 por segundo.
O tempo de CPU por solicitação, calculado pela divisão do uso da CPU pelo número de solicitações por segundo, foi reduzido de 13,4% para 23,7%.
Observe que o tempo de CPU por solicitação diminuiu, embora seu uso tenha aumentado por 2,37 segundos, que corresponde a 23,7% de utilização de um único núcleo de CPU, para 7,8 segundos, 78% de um núcleo de CPU.
Como usar perfis de heap alocada para melhorar o uso de recursos
Esta seção ilustra como você pode usar o heap e os perfis de heap alocados para identificar um método de uso intensivo de alocação no aplicativo:
Os perfis de pilha mostram a quantidade de memória alocada no heap do programa no momento em que o perfil é coletado.
Os perfis de heap alocados mostram a quantidade total de memória que foi alocada no heap do programa durante o intervalo em que o perfil foi coletado. Ao dividir esses valores por 10 segundos, o intervalo de coleta de perfis, é possível interpretá-los como taxas de alocação.
Como ativar a coleta de perfis de heap
Execute o aplicativo com a versão do aplicativo definida como
3
e ative a coleção de perfis de heap e de heap alocados.go run . -version 3 -num_rounds 40 -heap -heap_alloc
Aguarde a conclusão do aplicativo e, em seguida, visualize os dados do perfil para essa versão do aplicativo:
- Clique em AGORA para carregar os dados mais recentes do perfil.
- No menu Versão, selecione 3.
- No menu Tipo de criador de perfil, selecione Heap alocada.
Por exemplo, o gráfico de chama é:
Como identificar a taxa de alocação de heap
O frame raiz exibe a quantidade total de heap que foi alocada durante os 10 segundos em que um perfil foi coletado, na média de todos os perfis. Nesse exemplo, o frame raiz mostra que, em média, 1,535 GiB de memória foi alocado.
Como modificar o aplicativo
Etapa 1: vale a pena minimizar a taxa de alocação de heap?
O uso de tempo de CPU da função de coleta de lixo de segundo plano do Go,
runtime.gcBgMarkWorker.*
, pode servir para determinar se vale
a pena otimizar um aplicativo para reduzir os custos
de coleta de lixo:
- Pule a otimização se o uso de tempo de CPU for menor que 5%.
- Otimize se o uso de tempo de CPU chegar a pelo menos 25%.
Neste exemplo, o uso de tempo de CPU do
coletor de lixo em segundo plano é de 16,8%. Esse valor é alto o suficiente
para tentar otimizar o shakesapp/server.go
:

Etapa 2: qual função aloca muita memória?
O arquivo shakesapp/server.go
contém duas funções que podem
ser alvos de otimização:
GetMatchCount
e readFiles
. Para determinar
a taxa de alocação de memória para essas funções, defina o
Tipo de perfil como heap alocado e, em seguida, use o
list
Lista de funções de foco.
Nesse exemplo, a alocação total de heap para readFiles.func1
durante a coleta de perfil de 10 segundos é, em média, 1,045 GiB ou 68% da
memória alocada total. A alocação de heap própria durante a coleta de perfil
de 10 segundos é de 255,4 MiB:

Nesse exemplo, o método Go makeSlice
alocou 798,7 MiB
durante a coleta de perfil de 10 segundos, em média.
A maneira mais simples de reduzir essas alocações
é reduzir as chamadas para makeSlice
. A
função readFiles
chama
makeSlice
por meio de um método de biblioteca.
O resultado dessa análise sugere que é possível reduzir
a taxa de alocações de heap otimizando readFiles
.
Etapa 3: como o aplicativo pode ser alterado?
Uma opção é modificar o aplicativo para ler os arquivos uma vez e, em seguida, reutilizar o conteúdo. Por exemplo, é possível fazer as seguintes alterações:
- Definir uma variável global
files
para armazenar os resultados de leitura do arquivo inicial:var files []string
- Modificar
readFiles
para retornar antecipadamente quandofiles
for definido:func readFiles(ctx context.Context, bucketName, prefix string) ([]string, error) { // return if defined if files != nil { return files, nil } // Existing type resp struct { s string err error } ... // Save the result in the variable files files = make([]string, len(paths)) for i := 0; i < len(paths); i++ { r := <-resps if r.err != nil { return nil, r.err } files[i] = r.s } return files, nil }
Como avaliar a alteração
Para avaliar a alteração, faça o seguinte:
Execute o aplicativo com a versão definida como
4
:go run . -version 4 -num_rounds 60 -heap -heap_alloc
Aguarde a conclusão do aplicativo e, em seguida, visualize os dados do perfil para essa versão do aplicativo:
- Clique em AGORA para carregar os dados mais recentes do perfil.
- No menu Versão, selecione 4.
- No menu Tipo de criador de perfil, selecione Heap alocada.
Para quantificar o efeito da alteração de
readFiles
na taxa de alocação de heap, compare os perfis de heap alocados da versão 4 com os coletados para 3:A dica do frame raiz mostra que, com a versão 4, a quantidade média de memória alocada durante a coleta do perfil diminuiu em 1,301 GiB em comparação com a versão 3. A dica de
readFiles.func1
mostra uma diminuição de 1,045 GiB:Para quantificar o efeito na coleta de lixo, configure uma comparação de perfis de tempo de CPU. Na captura de tela a seguir, um filtro é aplicado para mostrar as pilhas do coletor de lixo do Go
runtime.gcBgMarkWorker.*
. A captura de tela mostra que o uso da CPU para coleta de lixo é reduzido para 4,97% de 16,8%.Para determinar se há um impacto da alteração no número de solicitações por segundo manipuladas pelo aplicativo, veja a saída no Cloud Shell. Neste exemplo, a versão 4 conclui até 15 solicitações por segundo, significativamente mais que as 5,8 solicitações por segundo da versão 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 ...
O aumento nas consultas por segundo exibido pelo aplicativo pode ocorrer devido à redução do tempo na coleta de lixo.
Você pode entender melhor o efeito da modificação para
readFiles
visualizando os perfis de heap. Uma comparação dos perfis da heap para a versão 4 com a versão 3 mostra que o uso de heap diminuiu para 18,47 MiB de 70,95 MiB:
Resumo
Neste guia de início rápido, os tempos de CPU e de heap alocados foram usados para identificar possíveis otimizações para um aplicativo. Os objetivos eram: maximizar o número de solicitações por segundo e eliminar alocações desnecessárias.
Ao usar perfis de tempo de CPU, uma função de uso intensivo da CPU foi identificada. Depois de aplicar uma alteração simples, a taxa de solicitação do servidor aumentou para 5,8 por segundo, de aproximadamente 1 por segundo.
Ao usar perfis de heap alocados, a função
shakesapp/server.go
readFiles
foi identificada como tendo uma alta taxa de alocação. Depois de otimizarreadFiles
, a taxa de solicitação do servidor aumentou para 15 solicitações por segundo e a quantidade média de memória alocada durante a coleta de 10 segundos de perfil diminuiu 1,301 GiB.
A seguir
Saiba mais sobre como os perfis são coletados e enviados para seu projeto doGoogle Cloud em Coleta de perfis.
Leia nossos recursos sobre DevOps e conheça nosso programa de pesquisa.
- Como criar perfis de aplicativos Go
- Como criar perfis de aplicativos Java
- Como criar perfis de aplicativos Node.js
- Como criar perfis de aplicativos Python
- Como criar perfis de aplicativos em execução fora do Google Cloud