Pruebas, Benchmark de pruebas

¿Qué es el benchmark?

El benchmark es el proceso mediante el cual podemos realizar una medición del performance de un programa o los componentes que lo conforman.

El paquete testing incluye un poderoso framework de benchmark. El número de veces que se invoca una función es controlado automáticamente por el framework y al final se reporta en la salida.

Supongamos que deseamos almacenar preparar un string que se va construyendo a través de un loop. En un ejemplo anterior vimos que los buffers son mas eficientes que utilizar el operador += de concatenación, o que almacenar un arreglo de strings y luego explotarlo. Pero como podemos verificar tal performance. Para ello preparamos 3 funciones que realizan la misma función, de cada una de las formas posibles.

Primero el paquete longstring dentro del archivo longstring.go.

package longstring

import (
    "bytes"
    "strings"
)

func MedianteConcatenacion(longitud int) string {
    var s string
    for i := 0; i < longitud; i++ {
        s += "texto"
    }
    return s
}

func MedianteArreglo(longitud int) string {
    s := []string{}
    for i := 0; i < longitud; i++ {
        s = append(s, "texto")
    }
    return strings.Join(s, "")
}

func MedianteBuffer(longitud int) string {
    var buffer bytes.Buffer
    for i := 0; i < longitud; i++ {
        buffer.WriteString("texto")
    }
    return buffer.String()
}

En este podemos ver que tenemos 3 funciones que hacen la misma operación de ir construyendo este string de salida. El número de ciclos realizados por el loop dependen del parámetro longitud.

  • MedianteConcatenacion
  • MedianteArreglo
  • MedianteBuffer

Ahora creamos las pruebas de benchmark, estas a diferencia de las pruebas de test tienen el prefijo Benchmark y reciben un parámetro *testing.B. El argumento contiene un valor N que representa el numero de veces que el loop se va a repetir, este valor es ajustado de forma automática por Golang. En cada uno de los casos invocamos cada uno de los distintos métodos.

package longstring

import "testing"

func BenchmarkMedianteConcatenacion(b *testing.B) {
    for i := 0; i < b.N; i++ {
        MedianteConcatenacion(100)
    }
}

func BenchmarkMedianteArreglo(b *testing.B) {
    for i := 0; i < b.N; i++ {
        MedianteArreglo(100)
    }
}

func BenchmarkMedianteBuffer(b *testing.B) {
    for i := 0; i < b.N; i++ {
        MedianteBuffer(100)
    }
}

En la salida obtenemos los resultados del benchmark.

go test -bench=.
goos: darwin
goarch: amd64
pkg: .../benchmark
BenchmarkMedianteConcatenacion-8      200000          7916 ns/op
BenchmarkMedianteArreglo-8            500000          2464 ns/op
BenchmarkMedianteBuffer-8            1000000          1429 ns/op
PASS
ok      .../benchmark    4.378s

En el caso anterior podemos darnos cuenta que basado en el benchmark, el método mas idóneo es BenchmarkMedianteBuffer.