Otimize uma app Go

Neste tutorial, implementa uma aplicação Go intencionalmente ineficiente que está configurada para recolher dados de perfil. Usa a interface do criador de perfis para ver os dados do perfil e identificar potenciais otimizações. Em seguida, modifica a aplicação, implementa-a e avalia o efeito da modificação.

Antes de começar

  1. 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.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  4. Enable the required API.

    Roles required to enable APIs

    To enable APIs, you need the Service Usage Admin IAM role (roles/serviceusage.serviceUsageAdmin), which contains the serviceusage.services.enable permission. Learn how to grant roles.

    Enable the API

  5. Para abrir o Cloud Shell, na Google Cloud barra de ferramentas da consola, clique em Ativar Cloud Shell:

    Ative o Cloud Shell.

    Após alguns momentos, é aberta uma sessão do Cloud Shell na Google Cloud consola:

    Sessão do Cloud Shell.

  6. Aplicação de exemplo

    O objetivo principal é maximizar o número de consultas por segundo que o servidor pode processar. Um objetivo secundário é reduzir a utilização de memória eliminando atribuições de memória desnecessárias.

    O servidor, através de uma framework gRPC, recebe uma palavra ou uma expressão e, em seguida, devolve o número de vezes que a palavra ou a expressão aparece nas obras de Shakespeare.

    O número médio de consultas por segundo que o servidor consegue processar é determinado através de testes de carga do servidor. Para cada ronda de testes, é chamado um simulador de cliente e são dadas instruções para emitir 20 consultas sequenciais. Quando uma ronda é concluída, são apresentados 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.

    Executar a aplicação de exemplo

    Transfira e execute a aplicação de exemplo:

    1. No Cloud Shell, execute os seguintes comandos:

      git clone https://github.com/GoogleCloudPlatform/golang-samples.git
      cd golang-samples/profiler/shakesapp
      
    2. Execute a aplicação com a versão definida como 1 e o número de rounds definido como 15:

      go run . -version 1 -num_rounds 15
      

      Após um ou dois minutos, são apresentados os dados do perfil. Os dados do perfil são semelhantes ao seguinte exemplo:

      Gráfico de chamas inicial para a utilização do tempo da CPU.

      Na captura de ecrã, repare que o Tipo de perfil está definido como CPU time. Isto indica que os dados de utilização da CPU são apresentados no gráfico de chamas.

      A saída de exemplo impressa no Cloud Shell é apresentada 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
      

      O resultado do Cloud Shell apresenta o tempo decorrido para cada iteração e a taxa de pedidos média. Quando a aplicação é iniciada, a entrada "Simulated 20 requests in 17.3s, rate of 1.156069 reqs / sec" indica que o servidor está a executar cerca de 1 pedido por segundo. Na última ronda, a entrada "Simulated 20 requests in 1m48.03s, rate of 0.185134 reqs / sec" indica que o servidor está a executar cerca de 1 pedido a cada 5 segundos.

    Usar perfis de tempo da CPU para maximizar as consultas por segundo

    Uma abordagem para maximizar o número de consultas por segundo é identificar métodos com utilização intensiva da CPU e otimizar as respetivas implementações. Nesta secção, usa perfis de tempo da CPU para identificar um método intensivo da CPU no servidor.

    Identificar a utilização do tempo da CPU

    O frame raiz do gráfico de chamas indica o tempo total da CPU usado pela aplicação durante o intervalo de recolha de 10 segundos:

    Vista expandida do frame raiz do gráfico de chamas.

    Neste exemplo, o serviço usado é 2.37 s. Quando o sistema é executado num único núcleo, uma utilização do tempo da CPU de 2,37 segundos corresponde a uma utilização de 23,7% desse núcleo. Para mais informações, consulte o artigo Tipos de criação de perfis disponíveis.

    Modificar a aplicação

    A avaliar a alteração

    Para avaliar a alteração, faça o seguinte:

    1. Execute a aplicação com a versão da aplicação definida como 2:

      go run . -version 2 -num_rounds 40
      

      Uma secção posterior mostra que, com a otimização, o tempo necessário para executar uma única ronda é muito inferior ao da aplicação não modificada. Para garantir que a aplicação é executada durante tempo suficiente para recolher e carregar perfis, o número de ciclos é aumentado.

    2. Aguarde pela conclusão da aplicação e, em seguida, veja os dados do perfil para esta versão da aplicação:

      • Clique em AGORA para carregar os dados do perfil mais recentes. Para mais informações, consulte o artigo Intervalo de tempo.
      • No menu Versão, selecione 2.

    Por exemplo, o gráfico de chamas é apresentado da seguinte forma:

    Gráfico de chamas que mostra a utilização do tempo da CPU da versão 2.

    Nesta figura, o frame raiz 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 pela aplicação aumentou de 2,37 segundos para 7,8 segundos, ou a aplicação passou a usar 23,7% de um núcleo da CPU para usar 78% de um núcleo da CPU.

    A largura do frame é uma medida proporcional da utilização do tempo da CPU. Neste exemplo, a largura da moldura para GetMatchCount indica que a função usa cerca de 49% de todo o tempo da CPU usado pela aplicação. No gráfico de chamas original, este mesmo frame representava cerca de 72% da largura do gráfico. Para ver a utilização exata do tempo da CPU, pode usar a sugestão de ferramenta de frames ou a lista de funções de foco:

    Lista de funções de foco que mostra a utilização do tempo da CPU da versão 2.

    O resultado no Cloud Shell mostra que a versão modificada está a concluir cerca de 5,8 pedidos 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 à aplicação teve dois efeitos diferentes:

    • O número de pedidos por segundo aumentou de menos de 1 por segundo para 5,8 por segundo.

    • O tempo da CPU por pedido, calculado dividindo a utilização da CPU pelo número de pedidos por segundo, diminuiu de 23,7% para 13,4%.

      Tenha em atenção que o tempo da CPU por pedido diminuiu, apesar de a utilização do tempo da CPU ter aumentado de 2,37 segundos, o que corresponde a 23,7% de utilização de um único núcleo da CPU, para 7,8 segundos, ou 78% de um núcleo da CPU.

    Usar perfis de memória atribuídos para melhorar a utilização de recursos

    Esta secção ilustra como pode usar os perfis de memória dinâmica e de memória dinâmica alocada para identificar um método com utilização intensiva de alocações na aplicação:

    • Os perfis de memória mostram a quantidade de memória atribuída na memória do programa no instante em que o perfil é recolhido.

    • Os perfis de memória atribuída mostram a quantidade total de memória que foi atribuída na memória dinâmica do programa durante o intervalo em que o perfil foi recolhido. Ao dividir estes valores por 10 segundos, o intervalo de recolha de perfis, pode interpretá-los como taxas de atribuição.

    Ativar a recolha de perfis de memória

    1. Execute a aplicação com a versão da aplicação definida como 3 e ative a recolha de perfis de memória dinâmica e memória dinâmica alocada.

      go run . -version 3 -num_rounds 40 -heap -heap_alloc
      
    2. Aguarde pela conclusão da aplicação e, em seguida, veja os dados do perfil para esta versão da aplicação:

      • Clique em AGORA para carregar os dados do perfil mais recentes.
      • No menu Versão, selecione 3.
      • No menu Tipo de Profiler, selecione Heap alocado.

      Por exemplo, o gráfico de chamas é apresentado da seguinte forma:

      Gráfico de rastreio em pilhas sobrepostas de perfis de memória atribuídos para a versão 3.

    Identificar a taxa de atribuição de memória

    O frame raiz apresenta a quantidade total de memória dinâmica que foi alocada durante os 10 segundos em que um perfil foi recolhido, calculada com base na média de todos os perfis. Neste exemplo, o frame raiz mostra que, em média, foram alocados 1,535 GiB de memória.

    Modificar a aplicação

    A avaliar a alteração

    Para avaliar a alteração, faça o seguinte:

    1. Execute a aplicação com a versão da aplicação definida como 4:

      go run . -version 4 -num_rounds 60 -heap -heap_alloc
      
    2. Aguarde pela conclusão da aplicação e, em seguida, veja os dados do perfil para esta versão da aplicação:

      • Clique em AGORA para carregar os dados do perfil mais recentes.
      • No menu Versão, selecione 4.
      • No menu Tipo de Profiler, selecione Heap alocado.
    3. Para quantificar o efeito da alteração de readFiles na taxa de atribuição de memória, compare os perfis de memória atribuída da versão 4 com os recolhidos para a versão 3:

      Comparação dos perfis de memória dinâmica alocada entre as versões 4 e 3.

      A dica de ferramenta da frame raiz mostra que, com a versão 4, a quantidade média de memória alocada durante a recolha de perfis diminuiu 1,301 GiB, em comparação com a versão 3. A sugestão de readFiles.func1 mostra uma diminuição de 1045 GiB:

      Comparação da sugestão de readfiles para o tipo de perfil de memória dinâmica alocada.

    4. Para quantificar o efeito na recolha de lixo, configure uma comparação dos perfis de tempo de CPU. Na captura de ecrã seguinte, é aplicado um filtro para mostrar as pilhas do coletor de lixo do Go runtime.gcBgMarkWorker.*. A captura de ecrã mostra que a utilização da CPU para a recolha de lixo foi reduzida de 16,8% para 4,97%.

      Comparação da utilização do tempo da CPU do processo de recolha de lixo em segundo plano da v4 com a v3.

    5. Para determinar se existe um impacto da alteração no número de pedidos por segundo processados pela aplicação, veja o resultado no Cloud Shell. Neste exemplo, a versão 4 conclui até 15 pedidos por segundo, o que é substancialmente superior aos 5,8 pedidos 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 publicadas pela aplicação pode dever-se a menos tempo gasto na recolha de lixo.

    • Pode compreender melhor o efeito da modificação a readFiles ao ver os perfis de memória. Uma comparação dos perfis de memória dinâmica da versão 4 com os da versão 3 mostra que a utilização de memória dinâmica diminuiu de 70,95 MiB para 18,47 MiB:

      Comparação da utilização da memória dinâmica para a versão 4 com a versão 3.

    Resumo

    Neste início rápido, foram usados perfis de tempo de CPU e de memória dinâmica alocada para identificar potenciais otimizações a uma aplicação. Os objetivos eram maximizar o número de pedidos por segundo e eliminar as atribuições desnecessárias.

    • Ao usar perfis de tempo da CPU, foi identificada uma função com utilização intensiva da CPU. Após aplicar uma alteração simples, a taxa de pedidos do servidor aumentou para 5,8 por segundo, em comparação com cerca de 1 por segundo.

    • Ao usar perfis de memória dinâmica alocada, a função shakesapp/server.go readFiles foi identificada como tendo uma taxa de alocação elevada. Após a otimização de readFiles, a taxa de pedidos do servidor aumentou para 15 pedidos por segundo e a quantidade média de memória atribuída durante a recolha de perfis de 10 segundos diminuiu 1,301 GiB.

    O que se segue?

    Para obter informações sobre a execução do agente do Cloud Profiler, consulte: