I Hashed Every Word Twice to Count It Once

La scorsa settimana ho analizzato le prestazioni di un parser di log. Conta i codici di errore in un log giornaliero. La logica principale è un semplice aggiornamento di un dizionario.

La maggior parte degli sviluppatori lo scrive così:

if (counts.TryGetValue(code, out int c))
    counts[code] = c + 1;
else
    counts[code] = 1;

Questo codice calcola l'hash della chiave due volte. TryGetValue trova la voce calcolando l'hash e percorrendo il bucket. Successivamente, l'indexer esegue esattamente lo stesso lavoro per aggiornare il valore. Si sprecano cicli di CPU sulla stessa chiave e sullo stesso hash due volte per ogni token.

Puoi saltare il secondo passaggio. Usa CollectionsMarshal.GetValueRefOrAddDefault.

ref int slot = ref CollectionsMarshal.GetValueRefOrAddDefault(counts, code, out _);
slot++;

Questo metodo trova o crea lo slot in un unico passaggio. Ti fornisce un riferimento diretto allo storage. Esegui un solo hash e un solo percorso nel bucket. Poi muti il valore direttamente sul posto.

Ho testato questo approccio con 5 milioni di token.

Risultati: • TryGetValue + indexer: 160,0 ms • GetValueRefOrAddDefault: 95,0 ms

La versione con un singolo lookup è 1,7 volte più veloce.

Fondamentalmente, le allocazioni di memoria sono rimaste identiche. Questo trucco non risparmia memoria. Risparmia solo CPU. Se il codice è lento a causa del garbage collection, questo cambiamento non serve a nulla. Se il codice è lento a causa di un intenso conteggio, questo aiuta.

Usa questo metodo quando il tuo ciclo esegue molti aggiornamenti su chiavi esistenti. Il vantaggio aumenta all'aumentare del rapporto tra aggiornamenti e inserimenti.

Un avvertimento: il riferimento punta allo storage interno del dizionario. Rimane valido solo fino alla prossima modifica strutturale. Non mantenere il riferimento se aggiungi o rimuovi chiavi. Prendi il ref, cambia il valore e vai avanti.

Usa TryGetValue per la maggior parte dei compiti. È più facile da leggere. Usa la versione con ref solo quando il ciclo del dizionario rappresenta un collo di bottiglia per le prestazioni.

Fonte: https://dev.to/ssukhpinder/i-hashed-every-word-twice-to-count-it-once-1mg9