pprof es la herramienta estándar para realizar un CPU profiling en Go, permitiéndote visualizar qué partes de tu programa están consumiendo ciclos de procesamiento. A diferencia de un debugger, que intercepta cada instrucción y ralentiza el programa drásticamente, pprof utiliza muestreo estadístico (sampling). El runtime de Go interrumpe la ejecución del hilo en intervalos regulares para observar el estado de la CPU; si una función aparece repetidamente en estas interrupciones, es una señal clara de que es un punto caliente. Esto es clave porque el muestreo minimiza el overhead, permitiéndote analizar código casi en condiciones de producción.
Debes entender que lo que obtienes no es un cronómetro exacto de cada llamada, sino una representación probabilística de dónde pasa el tiempo la CPU. Por eso, no debes usarlo para medir funciones que se ejecutan en nanosegundos de forma muy esporádica, ya que el muestreo podría no capturarlas. Úsalo realmente cuando los benchmarks te indiquen que algo es lento, pero el código parece correcto, o cuando necesites identificar cuellos de botella inesperados en servicios de alta carga. Si intentas tratar un profile de CPU como si fuera un log de ejecución paso a paso, perderás el tiempo optimizando funciones que no son el verdadero problema.
Para generar los datos necesarios, primero necesitas un benchmark que estrese el sistema y luego ejecutarlo con el flag -cpuprofile.
package example_test
import (
"crypto/sha256"
"testing"
)
// computeHash representa una carga de trabajo pesada de CPU.
func computeHash(data []byte) [32]byte {
return sha256.Sum256(data)
}
// wrapper simula una función de alto nivel que llama a funciones internas.
func wrapper(data []byte) [32]byte {
return computeHash(data)
}
// BenchmarkHeavyWork es el punto de entrada para nuestro profiling.
func BenchmarkHeavyWork(b *testing.B) {
data := make([]byte, 1024) // 1KB de datos
for i := 0; i < b.N; i++ {
wrapper(data)
}
}
Para analizar este ejemplo, primero ejecuta en tu terminal:
go test -bench=. -cpuprofile=cpu.prof
Luego, entra en la herramienta interactiva:
go tool pprof cpu.prof
Una vez dentro de la consola de pprof, los comandos que vas a usar son tus mejores aliados:
top: Te muestra una lista de las funciones que más recursos consumen. Aquí verás dos columnas críticas:flatycumulative.flates el tiempo que la CPU pasó ejecutando código dentro de esa función específica.cumulativees el tiempo que la CPU pasó en esa función más el tiempo que pasó en todas las funciones que ella misma llamó. En nuestro ejemplo,wrappertendrá uncumulativealto, pero suflatserá casi cero, porque su trabajo es solo delegar.computeHash, en cambio, tendrá unflatalto porque es donde ocurre el trabajo real.
list <FunctionName>: Este es el comando más potente para el debugging de rendimiento. Si haceslist computeHash,pprofte mostrará el código fuente de esa función y, al lado, cuántos milisegundos (o muestras) se dedicaron a cada línea. Te permite ver exactamente qué línea de código está “quemando” el CPU.web: Si tienes instaladographviz, este comando abrirá una pestaña en tu navegador con un gráfico de flujo. Los nodos más grandes y con flechas más gruesas indican el camino por el que fluye la mayor parte de la carga de CPU. Es ideal para entender la jerarquía de llamadas a nivel macro.
El error frecuente ocurre cuando un desarrollador se obsesiona con las funciones que tienen el cumulative más alto. Si ves que wrapper aparece en el top con un tiempo acumulado enorme, podrías pensar erróneamente que el problema es la lógica de wrapper. Sin embargo, si miras el flat, verás que la función es casi inexistente en términos de cómputo. El cuello de botella está en la función que wrapper invoca. Optimizar la lógica de una función con flat bajo pero cumulative alto es una pérdida de tiempo; lo que debes atacar es la función con el flat más alto.
N° 163