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
