Riflessioni sul benchmark tra versioni di un programma Windows

· Aggiornato il: · · Windows, Benchmark, Performance, Misurazione, C# / .NET

Quando un cliente dice “ho testato due versioni del programma, e quella che sembra più veloce dà risultati peggiori”, il problema spesso non è il programma, ma il benchmark.

Nello sviluppo di app Windows, ci sono troppe cose che “fanno rumore” intorno alla misurazione. Il programma A sembra più veloce, ma il benchmark dice il contrario. Al contrario, il programma B fa un bel punteggio in un loop sintetico, ma nella pratica sembra lento. In questi casi, la maggior parte delle volte non c’è contraddizione: semplicemente il benchmark sta misurando qualcosa di diverso da ciò che conta all’utente.

In questo articolo non forniamo uno strumento di benchmark, ma organizziamo le decisioni da prendere prima di far partire il cronometro.

1. La conclusione prima di tutto

  • Il vero obiettivo del benchmark non è “chi fa più punti”, ma “cosa si vuole migliorare per l’utente”.
  • Se quello che vuoi misurare è “il tempo dall’azione dell’utente al risultato”, non serve wall-clock rispetto a un singolo metodo; serve misurare l’intero percorso.
  • Le app Windows desktop hanno molti fattori esterni: antimalware, indicizzazione, notifiche, pianificazione CPU, Wi-Fi, aggiornamenti, stato della batteria.
  • Per questo, non confrontare una singola esecuzione: confrontare la distribuzione sotto condizioni registrate.
  • La differenza tra CPU time e wall-clock è importante. A volte un programma sembra più veloce non perché abbia fatto meno lavoro, ma perché ha lavorato asincrono o ha nascosto l’attesa.
  • In conclusione, il benchmark più pericoloso è quello che dà un singolo numero e sembra oggettivo.

2. Cosa si sta confrontando davvero?

Prima di tutto, chiariamo i due concetti base.

2.1. Confronto tra versioni di uno stesso programma

In genere si vuol sapere se un refactoring o un aggiornamento ha migliorato o peggiorato qualcosa. L’ipotesi è che il carico di lavoro sia più o meno lo stesso.

Versione 1.0Versione 1.1Stesso compito

Qui la cosa importante è fissare lo stesso input, lo stesso percorso, le stesse condizioni. Se il programma 1.1 è più lento ma fa più cose, non è un benchmark leale.

2.2. Confronto tra programmi diversi

A volte il benchmark è tra due programmi che fanno lo stesso lavoro, magari con architetture diverse. In questo caso, bisogna definire cosa significhi “stesso lavoro”. Ad esempio, se uno produce un output immediato e non mostra progresso, mentre l’altro sembra più lento ma il risultato finale arriva prima, la metrica “prima risposta” dà giudizi diversi dalla metrica “completamento”.

Programma AProgramma BCompito comparabile

La cosa più importante qui è non prendere per buono che “stesso input” implichi “stesso obiettivo”. Bisogna specificare l’output, il tempo misurato e l’ambiente.

3. Scegliere la metrica giusta

Molti problemi di benchmark nascono dal fatto che si misura una cosa e si discute di un’altra.

3.1. Wall-clock time

Il tempo trascorso dal punto di vista dell’utente. Include CPU, I/O, attese, context switch, tutto.

Va bene quando:

  • vuoi la sensazione utente;
  • vuoi confrontare scenari end-to-end.

Attenzione a:

  • rumore di sistema;
  • differenze di hardware;
  • varianza causata da background.

3.2. CPU time

Il tempo in cui la CPU è effettivamente occupata a eseguire il processo.

Va bene quando:

  • vuoi misurare se una modifica algoritmica ha ridotto il lavoro computazionale;
  • vuoi isolare il costo del programma da fattori esterni.

Non va bene quando:

  • vuoi la sensazione di latenza, perché esclude attese I/O o scheduling;
  • il programma parallelizza o fa asincrono.

3.3. I/O time / throughput

Tempo speso su disco, rete, database.

Va bene quando:

  • il collo di bottiglia è chiaramente I/O;
  • vuoi misurare quanti record al secondo processi, quante richieste servisci.

3.4. Time-to-visible / time-to-interactive

Quanto passa dall’azione dell’utente al momento in cui vede qualcosa a schermo.

Va bene quando:

  • vuoi misurare percezione di reattività;
  • l’app ha splash screen, finestre, progress bar.

3.5. Il problema con una sola metrica

Un singolo numero non basta quasi mai. La sensazione utente è multidimensionale:

  • Il programma A mostra subito una finestra, ma impiega 10 secondi a caricare i dati.
  • Il programma B impiega 2 secondi a mostrare la finestra, ma poi è immediato.
  • Quale è più veloce? Dipende da cosa conta per l’utente in quel task.
1s10s2s0sProgramma AFinestra visibileDati completiProgramma BFinestra visibileDati completi

Per questo, registrare più punti temporali lungo il percorso dà un quadro molto più fedele di un singolo punteggio finale.

4. Fonti di disturbo comuni su Windows

Windows non è un sistema che si ferma quando si lancia un benchmark. Ecco alcuni dei fattori più comuni che distorcono le misurazioni.

4.1. Antimalware e sicurezza

Microsoft Defender e altri software di sicurezza possono scansionare i file eseguibili, le DLL caricate o le scritture su disco. Questo è bene per la sicurezza, ma un disastro per la riproducibilità.

scansione in backgroundAvvio benchmarkAntimalware intercettaRitardo variabile

In un benchmark tra versioni, se una versione ha un nuovo eseguibile o DLL non visti prima, l’AV potrebbe rallentare solo quella versione.

4.2. Indicizzazione della ricerca

L’indicizzazione di Windows Search può attivarsi quando vengono creati o modificati file. Se il benchmark genera file temporanei, l’indicizzatore può attaccarsi e falsare i tempi di I/O.

4.3. Notifiche e app in background

Microsoft Teams, Outlook, OneDrive, aggiornamenti, notifiche. Questi processi competono per CPU e I/O. Anche una singola notifica può causare un picco di attività nel momento sbagliato.

4.4. Pianificazione CPU e power management

Su CPU moderne con P-core e E-core, o su laptop con risparmio energetico, lo scheduling e la frequenza della CPU cambiano continuamente. Un programma può finire su un core efficiente in un run e su un core performante in un altro.

4.5. Cache e stato precedente

La seconda esecuzione di un programma è spesso più veloce della prima perché i file sono in cache, le DLL sono già caricate, il JIT ha già compilato, il database ha le pagine in buffer. Se non si controlla, si confrontano run con e senza cache.

5. Cosa controllare prima di misurare

Ecco una checklist pratica per ridurre il rumore.

# Elemento Azione
1 Build Usa sempre build Release, non Debug
2 Avvio pulito Riavvia il sistema o almeno chiudi le app non necessarie
3 Do Not Disturb Disabilita notifiche durante il benchmark
4 Ricerca Disabilita temporaneamente l’indicizzazione se il test genera molti file
5 Rete Scollega Wi-Fi / Ethernet se non serve, per evitare sincronizzazioni
6 Alimentazione Imposta piano di alimentazione ad alte prestazioni, collega il caricatore sui laptop
7 Processi Registra l’elenco dei processi attivi per ogni run
8 Temperatura Assicurati che la CPU non stia thermal-throttling
9 Ripetizioni Esegui multipli run e guarda la distribuzione
10 Documentazione Annota build, OS, CPU, RAM, piano energetica, processi attivi

6. Come raccogliere i dati

6.1. Non fidarti di una singola esecuzione

Una singola misura può essere influenzata da tutto il rumore sopra. Raccogli molte osservazioni e guarda:

  • mediana
  • media
  • deviazione standard
  • percentili (p50, p90, p95, p99)
  • min / max

6.2. Rappresentare la varianza

Un grafico con barre d’errore o un box plot è molto più utile di una tabella con solo medie. Se due versioni hanno media simile ma varianza diversa, l’utente percepisce molto bene quella più stabile.

Confronto tra versioniVersione 1Versione 2Distribuzione ampiamediocreDistribuzione strettaveloce

6.3. Misurare più fasi

Dividi il percorso utente in fasi e cronometra ciascuna. Ad esempio, per l’apertura di un documento:

  1. Clic su file
  2. Processo avviato
  3. Finestra visibile
  4. UI interattiva
  5. Dati caricati
  6. Rendering completato

Se confronti due versioni solo sul totale, perdi informazioni su dove si è guadagnato o perso.

7. Casi in cui i benchmark mentono

7.1. Il benchmark sintetico non assomiglia al carico reale

Un loop che esegue un milione di volte la stessa funzione può dare numeri molto diversi da un uso reale con dati variabili, cache calde e fredde, e branching non predicibile.

7.2. Il compilatore ha ottimizzato via il codice

In benchmark micro, il compilatore JIT o AOT può eliminare il codice che pensa inutile. Assicurati di consumare il risultato.

// Esempio: il compilatore potrebbe eliminare il calcolo se non usi result
for (int i = 0; i < N; i++)
{
    var result = Compute(i);
    // senza usare result, l'intero loop potrebbe essere rimosso
}

7.3. La misura include costi fissi

Se misuri “tempo totale” per un’operazione che dura pochi millisecondi, il tempo di avvio del processo o il cold start della JIT domina il risultato.

7.4. Confrontare run con stati termici diversi

Su laptop, il primo run può essere veloce perché la CPU è fredda, il decimo run può rallentare per thermal throttling. Documenta la temperatura o attendi lo stabilizzarsi.

8. Strumenti utili su Windows

Strumento Uso
System.Diagnostics.Stopwatch Cronometraggio ad alta precisione nel codice
QueryPerformanceCounter / GetProcessTimes Misure native di tempo e CPU
Windows Performance Recorder (WPR) Traccia CPU, I/O, rete, con elevata granularità
Windows Performance Analyzer (WPA) Analisi delle tracce WPR
dotnet trace / PerfView Profilazione .NET
Process Explorer Controlla CPU, memoria, handle, DLL caricate
ETW / Event Tracing for Windows Tracciamento a basso overhead

9. Riassunto

Per fare un benchmark tra versioni di un programma Windows che significhi qualcosa:

  1. Definisci cosa vuoi misurare (latenza, throughput, CPU, tempo a schermo).
  2. Fissa le condizioni (build Release, ambiente pulito, piano energetico, multipli run).
  3. Scegli la metrica adatta al tuo obiettivo, non quella più facile da ottenere.
  4. Misura l’intero percorso utente, non solo un metodo interno.
  5. Raccogli la distribuzione, non una singola osservazione.
  6. Guarda percentili e varianza, non solo la media.
  7. Documenta ambiente e fattori esterni.
  8. Sii sospettoso quando un benchmark sembra troppo bello o troppo strano.

Il benchmark più pericoloso è quello che dà un numero e sembra oggettivo. Un buon benchmark racconta una storia che si allinea con la sensazione dell’utente.

10. Riferimenti

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Sviluppo di applicazioni Windows

Le app Windows che si confrontano tra versioni hanno molti punti di interferenza (AV, indicizzazione, notifiche, pianificazione CPU, WPR), quindi il supporto implementativo su misurazione e automatizzazione rientra nello sviluppo app Windows.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Perché un programma più veloce dà risultati peggiori in un benchmark?
Perché un benchmark misura un'istantanea del sistema, non necessariamente il vero costo del programma. Se un programma sembra più veloce in uso reale ma benchmarka peggio, spesso il benchmark non sta misurando ciò che conta all'utente. Possibili cause sono: misura solo wall-clock mentre lavora asincrono; include startup influenzato da cache, AV o caricamenti differiti; esegue poche iterazioni e cattura un outlier; non tiene conto della varianza di background (notifiche, update, indexing); oppure è strettamente CPU-bound mentre l'utente percepisce latenza I/O. Iniziare a documentare cosa si vuole misurare, poi scegliere l'unità (wall clock, CPU time, I/O time, tempo a schermo, throughput) e infine ripetere e registrare la distribuzione.
Quali fattori di ambiente Windows distorcono i benchmark?
Molti. Le notifiche e il Do Not Disturb possono interrompere il foreground; l'indicizzazione della ricerca di Windows attiva I/O e CPU in background; Microsoft Defender e altri AV scansionano file nuovi; le app in background (OneDrive, Teams, aggiornamenti) competono per risorse; lo scaling della CPU, il turbo boost e i P/E core moderni introducono varianza termica e di scheduling. Inoltre, la priorità del processo, l'affinità CPU e il tempo di startup influenzano molto le misure. Per benchmark ripetibili: usa build Release, riavvia il sistema, chiudi app non necessarie, disabilita Do Not Disturb, scollega reti non necessarie, imposta piano di alimentazione ad alte prestazioni, lancia con priorità normale e registra temperatura / utilizzo CPU insieme ai tempi.
Migliore wall-clock o migliore CPU time?
Dipende da cosa si vuole misurare. Se vuoi la sensazione dell'utente (quanto ci ha messo da quando ha fatto l'azione a quando ha visto il risultato), wall-clock è corretto, a patto di misurare l'intero flusso utente. Se vuoi capire se una modifica algoritmica ha ridotto il lavoro della CPU, CPU time è più stabile perché non include attese I/O o scheduling. Nella maggior parte dei benchmark tra versioni di programmi Windows, conviene misurare entrambi e guardare la distribuzione, non la singola osservazione. Il problema spesso non è quale scegliere, ma prendere un'unica metrica e presentarla come verità.
Quante iterazioni servono per un benchmark significativo?
Non esiste un numero magico. La regola pratica è: ripeti finché la varianza si stabilizza e puoi stimare un intervallo di confidenza. Per programmi Windows desktop, almeno 10-30 iterazioni sono spesso necessarie per catturare la varianza di background. Scartare outlier a mano è pericoloso: meglio raccoglierne molti e guardare mediane, percentili (p50, p95, p99) e varianza. Se un test dura a lungo, usa meno iterazioni ma annota la deviazione standard. Registra anche le condizioni ambiente (build, OS, CPU, memoria, piano energia, processi attivi) così da poter riprodurre.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog