Benchmarks en Go: medir rendimiento con testing.B

Los benchmarks en Go son funciones del paquete testing que miden el rendimiento de tu código de forma reproducible. Al contrario que un simple time.Now() manual, el framework de benchmarks controla el número de iteraciones, calienta el código, descarta ejecuciones anómalas e informa de nanosegundos por operación y bytes por operación.

Estructura de un benchmark

// fichero: algoritmo_test.go
package algoritmo_test

import (
    "testing"
    "ejemplo.com/miapp/algoritmo"
)

func BenchmarkBusquedaLineal(b *testing.B) {
    datos := generarDatos(10000)
    b.ResetTimer() // excluye generarDatos del tiempo medido

    for i := 0; i < b.N; i++ {
        algoritmo.BusquedaLineal(datos, 9999)
    }
}

func BenchmarkBusquedaBinaria(b *testing.B) {
    datos := generarDatosOrdenados(10000)
    b.ResetTimer()

    for i := 0; i < b.N; i++ {
        algoritmo.BusquedaBinaria(datos, 9999)
    }
}

La clave es el bucle for i := 0; i < b.N; i++. El framework ajusta b.N automáticamente: comienza con 1 y va aumentando hasta que la duración total supera el tiempo mínimo (por defecto 1 segundo). Esto garantiza resultados estables.

Ejecutar benchmarks

# -bench acepta un patrón regex (. = todos los benchmarks)
go test -bench=. ./...

# solo benchmarks que coincidan con "Busqueda"
go test -bench=Busqueda ./...

# con información de memoria
go test -bench=. -benchmem ./...

# número de iteraciones fijo
go test -bench=. -benchtime=5s ./...
go test -bench=. -benchtime=100x ./...   # exactamente 100 iteraciones

Interpretar los resultados

BenchmarkBusquedaLineal-8   1000000   1234 ns/op   0 B/op   0 allocs/op
BenchmarkBusquedaBinaria-8  5000000    213 ns/op   0 B/op   0 allocs/op
  • -8 — número de CPUs usadas (GOMAXPROCS)
  • 1000000 — número de iteraciones que realizó b.N
  • 1234 ns/op — nanosegundos por operación
  • 0 B/op — bytes asignados en heap por operación (con -benchmem)
  • 0 allocs/op — número de allocations por operación (con -benchmem)

ResetTimer: excluir el setup del tiempo

func BenchmarkConSetup(b *testing.B) {
    // este setup puede tardar; no queremos medirlo
    cache := cargarCacheDesdeDB(1_000_000)

    b.ResetTimer() // el tiempo empieza a contar aquí

    for i := 0; i < b.N; i++ {
        cache.Get("clave-ejemplo")
    }
}

Comparar implementaciones

func BenchmarkConcatenarPlus(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s := ""
        for j := 0; j < 100; j++ {
            s += "x"
        }
        _ = s
    }
}

func BenchmarkConcatenarBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        for j := 0; j < 100; j++ {
            sb.WriteString("x")
        }
        _ = sb.String()
    }
}
BenchmarkConcatenarPlus-8    100000   15432 ns/op   5280 B/op   99 allocs/op
BenchmarkConcatenarBuilder-8 1000000   1021 ns/op    224 B/op    3 allocs/op

La diferencia en allocations explica la diferencia de rendimiento: concatenar con + en un bucle crea una nueva cadena en cada iteración.

pprof: profiling detallado

# genera un perfil de CPU
go test -bench=. -cpuprofile=cpu.out ./...
go tool pprof cpu.out

# genera un perfil de memoria
go test -bench=. -memprofile=mem.out ./...
go tool pprof mem.out

# interfaz web interactiva
go tool pprof -http=:8080 cpu.out

pprof muestra qué funciones consumen más tiempo CPU o asignan más memoria, lo que te guía para saber dónde optimizar.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP