Я хешував кожне слово двічі, щоб порахувати його лише один раз

Минулого тижня я профілював парсер логів. Він підраховує коди помилок у щоденному лозі. Основна логіка — це просте оновлення словника.

Більшість розробників пишуть це так:

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

Цей код хешує ключ двічі. TryGetValue знаходить запис, обчислюючи хеш і проходячи по бакету. Потім індексатор виконує ту саму роботу ще раз, щоб оновити значення. Ви марнуєте цикли процесора на той самий ключ і той самий хеш двічі для кожного токена.

Ви можете пропустити другий крок. Використовуйте CollectionsMarshal.GetValueRefOrAddDefault.

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

Цей метод знаходить або створює комірку за один крок. Він надає вам посилання безпосередньо на сховище. Ви виконуєте одне хешування та один прохід по бакету. Потім ви змінюєте значення безпосередньо на місці.

Я протестував це на 5 мільйонах токенів.

Результати: • TryGetValue + індексатор: 160,0 мс • GetValueRefOrAddDefault: 95,0 мс

Версія з одним пошуком у 1,7 раза швидша.

Важливо, що виділення пам'яті залишилися ідентичними. Цей трюк не економить пам'ять. Він економить лише ресурси процесора. Якщо ваш код працює повільно через збирання сміття (garbage collection), ця зміна нічого не дасть. Якщо ж код повільний через інтенсивний підрахунок, це допоможе.

Використовуйте це, коли ваш цикл виконує багато оновлень існуючих ключів. Перевага зростає зі збільшенням співвідношення оновлень до нових вставок.

Попередження: посилання вказує на внутрішнє сховище словника. Воно залишається дійсним лише до наступної структурної зміни. Не утримуйте посилання, якщо ви додаєте або видаляєте ключі. Отримайте ref, змініть значення і йдіть далі.

Використовуйте TryGetValue для більшості завдань. Це легше читати. Використовуйте версію з ref лише тоді, коли цикл зі словником є вузьким місцем у продуктивності.

Джерело: https://dev.to/ssukhpinder/i-hashed-every-word-twice-to-count-it-once-1mg9