Cómo optimizar una app de Go

En este instructivo, implementarás una aplicación de Go intencionalmente ineficiente que esté configurada para recopilar datos de perfil. Usa la interfaz de Profiler para ver los datos de perfil e identificar posibles optimizaciones. A continuación, modificarás la aplicación, la implementarás y evaluarás el efecto de la modificación.

Antes de comenzar

  1. Accede a tu cuenta de Google Cloud. Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

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

    Go to project selector

  4. Enable the required API.

    Enable the API

  5. Para abrir Cloud Shell, en la barra de herramientas de la consola de Google Cloud, haz clic en Activar Cloud Shell:

    Activa Cloud Shell.

    Después de unos momentos, se abrirá una sesión de Cloud Shell en la consola de Google Cloud:

    Sesión de Cloud Shell

Aplicación de muestra

El objetivo principal es maximizar la cantidad de consultas por segundo que el servidor puede procesar. Un objetivo secundario es reducir el uso de memoria cuando se eliminan las asignaciones de memoria innecesarias.

El servidor, mediante un framework de gRPC, recibe una palabra o frase y, luego, muestra la cantidad de veces que la palabra o frase aparece en las obras de Shakespeare.

La cantidad promedio de consultas por segundo que el servidor puede manejar se determina mediante una prueba de carga del servidor. Para cada ronda de pruebas, se llama a un simulador de cliente, y se le solicita que emita 20 consultas secuenciales. Cuando se completa una ronda, se muestra la cantidad de consultas que envió el simulador de cliente, el tiempo transcurrido y la cantidad promedio de consultas por segundo.

El código del servidor es intencionalmente ineficiente.

Ejecuta la aplicación de muestra

Descarga y ejecuta la aplicación de muestra:

  1. En Cloud Shell, ejecuta los siguientes comandos:

    git clone https://github.com/GoogleCloudPlatform/golang-samples.git
    cd golang-samples/profiler/shakesapp
    
  2. Ejecuta la aplicación con la versión establecida en 1 y la cantidad de rondas configuradas en 15:

    go run . -version 1 -num_rounds 15
    

    Después de un minuto o dos, se muestran los datos de perfil: Los datos del perfil son similares al siguiente ejemplo:

    Gráfico tipo llama inicial para el uso del tiempo de CPU

    En la captura de pantalla, observa que el Tipo de perfil está configurado como CPU time. Esto indica que los datos de uso de la CPU se muestran en el gráfico tipo llama.

    A continuación, se proporciona un resultado de muestra impreso en 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
    

    El resultado de Cloud Shell muestra el tiempo transcurrido para cada iteración y el porcentaje de solicitudes promedio: Cuando se inicia la aplicación, la entrada "Simulación de 20 solicitudes en 17.3 s, la tasa de 1.156069 solicitudes por segundo" indica que el servidor se ejecuta alrededor de 1 solicitud por segundo. En la última ronda, la entrada "Simulación de 20 solicitudes en 1m48.03 s, la tasa de 0.185134 solicitudes por segundo" indica que el servidor se está ejecutando a 1 solicitud cada 5 segundos.

Usa perfiles de tiempo de CPU para maximizar las consultas por segundo

Un enfoque para maximizar la cantidad de consultas por segundo es identificar los métodos con uso intensivo de CPU y optimizar sus implementaciones. En esta sección, usarás perfiles de tiempo de CPU para identificar un método de uso intensivo de CPU en el servidor.

Identifica el uso del tiempo de CPU

El marco raíz del gráfico tipo llama muestra una lista del tiempo total de CPU que usó la aplicación en el intervalo de recopilación de 10 segundos:

Vista expandida del marco raíz del gráfico tipo llama

En este ejemplo, el servicio usó 2.37 s. Cuando el sistema se ejecuta en un solo núcleo, un uso de tiempo de CPU de 2.37 segundos corresponde al uso del 23.7% de ese núcleo. Para obtener más información, consulta la sección sobre tipos de perfiles disponibles.

Modifica la aplicación

Evalúa el cambio

Para evaluar el cambio, haz lo siguiente:

  1. Ejecuta la aplicación con la versión de la aplicación establecida en 2:

    go run . -version 2 -num_rounds 40
    

    Más adelante, se muestra que, con la optimización, el tiempo que lleva ejecutar una sola ronda es mucho menor que el de la aplicación sin modificar. A fin de garantizar que la aplicación se ejecute lo suficiente para recopilar y subir perfiles, aumenta la cantidad de rondas.

  2. Espera a que se complete la aplicación y, luego, visualiza los datos de perfil de esta versión de la aplicación:

    • Haz clic en AHORA para cargar los datos de perfil más recientes. Para obtener más información, consulta Intervalo de tiempo.
    • En el menú Versión, selecciona 2.

Para un ejemplo, el gráfico tipo llama es el siguiente:

Gráfico tipo llama que muestra el uso del tiempo de CPU de la versión 2.

En esta figura, el marco raíz se muestra como un valor de 7.8 s. Como resultado de cambiar la función de coincidencia de strings, el tiempo de CPU que usó la aplicación aumentó de 2.37 segundos a 7.8 segundos, o la aplicación pasó de usar el 23.7% de un núcleo de CPU al usar el 78% de un núcleo de CPU.

El ancho del marco es una medida proporcional del uso del tiempo de CPU. En este ejemplo, el ancho del marco para GetMatchCount indica que la función usa aproximadamente el 49% de todo el tiempo de CPU que usa la aplicación. En el gráfico tipo llama original, este mismo marco era de aproximadamente el 72% del ancho del gráfico. Para ver el uso exacto del tiempo de CPU, puedes usar la información sobre la herramienta de marcos o usar la Lista de funciones de enfoque:

Lista de funciones de enfoque que muestra el uso de tiempo de CPU de la versión 2.

El resultado en Cloud Shell muestra que la versión modificada completa alrededor de 5.8 solicitudes 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

El pequeño cambio en la aplicación tuvo dos efectos diferentes:

  • La cantidad de solicitudes por segundo aumentó de menos de 1 por segundo a 5.8 por segundo.

  • El tiempo de CPU por solicitud, que se calcula dividiendo el uso de CPU por la cantidad de solicitudes por segundo, disminuyó del 23,7% al 13,4%.

    Ten en cuenta que el tiempo de CPU por solicitud disminuyó a pesar de que el uso de tiempo de CPU aumentó de 2.37 segundos, lo que corresponde a un 23.7% de uso de un solo núcleo de CPU, a 7.8 segundos o al 78% de un núcleo de CPU.

Usa perfiles de montón asignados para mejorar el uso de recursos

En esta sección, se ilustra cómo puedes usar el montón y los perfiles de montón asignados para identificar un método de asignación intensiva en la aplicación:

  • Los perfiles de montón muestran la cantidad de memoria asignada en el montón del programa en el instante en que se recopila el perfil.

  • Los perfiles de montón asignados muestran la cantidad total de memoria que se asignó en el montón del programa durante el intervalo en el que se recopiló el perfil. Si divides estos valores por 10 segundos, el intervalo de recopilación de perfil, puedes interpretarlos como tasas de asignación.

Habilita la recopilación de perfiles de montón

  1. Ejecuta la aplicación con la versión de la aplicación configurada en 3 y habilita la recopilación de montón y perfiles de montón asignados.

    go run . -version 3 -num_rounds 40 -heap -heap_alloc
    
  2. Espera a que se complete la aplicación y, luego, visualiza los datos de perfil de esta versión de la aplicación:

    • Haz clic en AHORA para cargar los datos de perfil más recientes.
    • En el menú Versión, selecciona 3.
    • En el menú Tipo de generador de perfiles, selecciona Montón asignado.

    Para un ejemplo, el gráfico tipo llama es el siguiente:

    Gráfico tipo llama de los perfiles de montón asignados para la versión 3.

Identifica la tasa de asignación de montón

El marco raíz muestra la cantidad total de montón que se asignó durante los 10 segundos en los que se recopiló un perfil, en promedio según todos los perfiles. En este ejemplo, el marco raíz muestra que, en promedio, se asignaron 1,535 GiB de memoria.

Modifica la aplicación

Evalúa el cambio

Para evaluar el cambio, haz lo siguiente:

  1. Ejecuta la aplicación con la versión de la aplicación establecida en 4:

    go run . -version 4 -num_rounds 60 -heap -heap_alloc
    
  2. Espera a que se complete la aplicación y, luego, visualiza los datos de perfil de esta versión de la aplicación:

    • Haz clic en AHORA para cargar los datos de perfil más recientes.
    • En el menú Versión, selecciona 4.
    • En el menú Tipo de generador de perfiles, selecciona Montón asignado.
  3. Para cuantificar el efecto del cambio de readFiles en la tasa de asignación de montón, compara los perfiles de montón asignados para la versión 4 con los recopilados para 3:

    Comparación de los perfiles de montón asignados entre las versiones 4 y 3.

    La información sobre la herramienta de marco raíz muestra que, con la versión 4, la cantidad promedio de memoria asignada durante la recopilación de perfiles disminuyó 1,301 GiB, en comparación con la versión 3. La información sobre la herramienta para readFiles.func1 muestra una disminución de 1,045 GiB:

    Comparación de la información sobre la herramienta de archivos de lectura para el tipo de perfil de montón asignado.

  4. Para cuantificar el efecto en la recopilación de elementos no utilizados, configura una comparación de los perfiles de tiempo de CPU. En la siguiente captura de pantalla, se aplica un filtro a fin de mostrar las pilas para la recopilación de elementos no utilizados de Go runtime.gcBgMarkWorker.*. La captura de pantalla muestra que el uso de CPU para la recopilación de elementos no utilizados pasó del 16.8% al 4.97%.

    Comparación del uso del tiempo de CPU del proceso de recopilación de elementos no utilizados en segundo plano de v4 a v3.

  5. Para determinar si hay un impacto en el cambio en el número de solicitudes por segundo manejadas por la aplicación, consulta el resultado en Cloud Shell. En este ejemplo, la versión 4 completa hasta 15 solicitudes por segundo, que es mucho más alta que las 5.8 solicitudes por segundo de la versión 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
    ...
    

    El aumento en las consultas por segundo que entrega la aplicación podría deberse a que se dedicó menos tiempo a la recopilación de elementos no utilizados.

  • Puedes obtener una comprensión más completa del efecto de la modificación en readFiles si ves los perfiles de montón. Una comparación de los perfiles de montón para la versión 4 con la versión 3 muestra que el uso de montón disminuyó a 18.47 MiB de 70.95 MiB:

    Comparación del uso de montón de la versión 4 a la versión 3.

Resumen

En esta guía de inicio rápido, se usaron el tiempo de CPU y los perfiles de montón asignados para identificar posibles optimizaciones en una aplicación. Los objetivos eran maximizar la cantidad de solicitudes por segundo y eliminar las asignaciones innecesarias.

  • Mediante los perfiles de tiempo de CPU, se identificó una función que consume mucha CPU. Después de aplicar un cambio simple, la tasa de solicitudes del servidor aumentó a 5.8 por segundo, hasta cerca de 1 por segundo.

  • Mediante el uso de perfiles de montón asignados, la función shakesapp/server.go readFiles se identificó como que tiene una tasa de asignación alta. Después de optimizar readFiles, la tasa de solicitudes del servidor aumentó a 15 solicitudes por segundo y la cantidad promedio de memoria asignada durante la recopilación de perfiles de 10 segundos disminuyó en 1,301 GiB.

¿Qué sigue?

Para obtener información sobre cómo ejecutar el agente de Cloud Profiler, consulta: