00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Benchmark

Che cosa sono i Benchmark?

I benchmark in Go permettono di misurare le prestazioni del codice in modo preciso e ripetibile. Il pacchetto testing include un supporto nativo per i benchmark, accessibili tramite il comando go test -bench. Sono fondamentali per prendere decisioni informate sull’ottimizzazione del codice.

Scrivere funzioni di Benchmark

Una funzione di benchmark deve iniziare con Benchmark, seguito da una lettera maiuscola, e accettare un parametro *testing.B:

// stringhe.go
package stringhe

import (
    "fmt"
    "strings"
)

func ConcatenaConPlus(parole []string) string {
    risultato := ""
    for _, p := range parole {
        risultato += p + " "
    }
    return risultato
}

func ConcatenaConBuilder(parole []string) string {
    var sb strings.Builder
    for _, p := range parole {
        sb.WriteString(p)
        sb.WriteString(" ")
    }
    return sb.String()
}

func ConcatenaConSprintf(parole []string) string {
    risultato := ""
    for _, p := range parole {
        risultato = fmt.Sprintf("%s%s ", risultato, p)
    }
    return risultato
}
// stringhe_test.go
package stringhe

import "testing"

var paroleTest = []string{
    "Go", "e", "un", "linguaggio", "di", "programmazione",
    "moderno", "efficiente", "e", "potente",
}

func BenchmarkConcatenaConPlus(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ConcatenaConPlus(paroleTest)
    }
}

func BenchmarkConcatenaConBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ConcatenaConBuilder(paroleTest)
    }
}

func BenchmarkConcatenaConSprintf(b *testing.B) {
    for i := 0; i < b.N; i++ {
        ConcatenaConSprintf(paroleTest)
    }
}

Il valore b.N

Il parametro b.N e il numero di iterazioni che il framework di benchmark determina automaticamente. Go aumenta progressivamente b.N fino a ottenere una misurazione stabile e affidabile. Non dovete mai impostare b.N manualmente: il framework lo gestisce per voi.

func BenchmarkEsempio(b *testing.B) {
    // b.N viene determinato automaticamente
    // Puo essere 1, 100, 10000, 1000000, ecc.
    for i := 0; i < b.N; i++ {
        // Operazione da misurare
        _ = fmt.Sprintf("iterazione %d", i)
    }
}

Eseguire i Benchmark

Per eseguire i benchmark si usa go test con il flag -bench:

# Esegue tutti i benchmark
go test -bench=.

# Esegue benchmark specifici (con regex)
go test -bench=BenchmarkConcatenaConBuilder

# Esegue con piu iterazioni per risultati stabili
go test -bench=. -benchtime=5s

# Esegue benchmark senza i test normali
go test -bench=. -run=^$

# Esegue con un numero specifico di iterazioni
go test -bench=. -benchtime=1000x

Leggere i risultati

L’output di un benchmark ha questo formato:

BenchmarkConcatenaConPlus-8      1000000    1150 ns/op
BenchmarkConcatenaConBuilder-8   5000000     215 ns/op
BenchmarkConcatenaConSprintf-8    500000    3200 ns/op
  • Nome-8: il nome del benchmark e il numero di CPU utilizzate (GOMAXPROCS).
  • 1000000: il numero di iterazioni eseguite (b.N finale).
  • 1150 ns/op: il tempo medio per operazione in nanosecondi.

Benchmark della memoria

Per misurare le allocazioni di memoria, si usa b.ReportAllocs() o il flag -benchmem:

func BenchmarkConcatenaConPlusMem(b *testing.B) {
    b.ReportAllocs() // Riporta le allocazioni di memoria
    for i := 0; i < b.N; i++ {
        ConcatenaConPlus(paroleTest)
    }
}

func BenchmarkConcatenaConBuilderMem(b *testing.B) {
    b.ReportAllocs()
    for i := 0; i < b.N; i++ {
        ConcatenaConBuilder(paroleTest)
    }
}

Oppure dalla riga di comando:

go test -bench=. -benchmem

L’output includera informazioni aggiuntive:

BenchmarkConcatenaConPlus-8      1000000  1150 ns/op   480 B/op  9 allocs/op
BenchmarkConcatenaConBuilder-8   5000000   215 ns/op   112 B/op  2 allocs/op
  • 480 B/op: byte allocati per operazione.
  • 9 allocs/op: numero di allocazioni per operazione.

Setup nel Benchmark

Quando il benchmark richiede una fase di preparazione, possiamo usare b.ResetTimer():

func BenchmarkConSetup(b *testing.B) {
    // Fase di setup (non misurata)
    dati := make([]int, 10000)
    for i := range dati {
        dati[i] = i
    }

    b.ResetTimer() // Resetta il timer dopo il setup

    for i := 0; i < b.N; i++ {
        // Operazione da misurare
        somma := 0
        for _, v := range dati {
            somma += v
        }
    }
}

Per operazioni costose che si ripetono ad ogni iterazione:

func BenchmarkConStopStart(b *testing.B) {
    for i := 0; i < b.N; i++ {
        b.StopTimer()
        dati := preparaDati() // Non misurato
        b.StartTimer()

        elabora(dati) // Misurato
    }
}

Sub-Benchmark

Come per i test, possiamo usare b.Run per creare sotto-benchmark parametrizzati:

func BenchmarkConcatenazione(b *testing.B) {
    dimensioni := []int{10, 100, 1000}

    for _, n := range dimensioni {
        parole := make([]string, n)
        for i := range parole {
            parole[i] = "parola"
        }

        b.Run(fmt.Sprintf("Plus/%d", n), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                ConcatenaConPlus(parole)
            }
        })

        b.Run(fmt.Sprintf("Builder/%d", n), func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                ConcatenaConBuilder(parole)
            }
        })
    }
}

Confrontare i Benchmark

Per confrontare benchmark tra diverse versioni del codice, possiamo salvare i risultati e usare strumenti come benchstat:

# Salva i risultati prima della modifica
go test -bench=. -count=10 > prima.txt

# Dopo la modifica
go test -bench=. -count=10 > dopo.txt

# Confronta con benchstat
go install golang.org/x/perf/cmd/benchstat@latest
benchstat prima.txt dopo.txt

L’output di benchstat mostra le variazioni percentuali e la significativita statistica delle differenze.

Conclusione

I benchmark sono uno strumento essenziale per ottimizzare il codice Go in modo scientifico. Permettono di misurare tempi di esecuzione e allocazioni di memoria, confrontare approcci diversi e verificare che le ottimizzazioni producano reali miglioramenti. Ricordate di eseguire i benchmark in condizioni controllate, usare -benchtime per risultati stabili e -benchmem per monitorare le allocazioni di memoria.