-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Как в FrozenDictionary подсчитываются коллизии хэша
- Loading branch information
Showing
6 changed files
with
65 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
_posts/2024/09/2024-09-09-frozen-dictionary-collisions-count.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
--- | ||
layout: post | ||
title: "Как в FrozenDictionary подсчитываются коллизии хэша" | ||
date: 2024-09-07 | ||
tags: csharp benchmark hashtable algorithms | ||
--- | ||
|
||
Второй короткий пост о деталях реализации `FrozenDictionary`, которые остались за кадром. Сегодня об [алгоритме подсчёта количества коллизий хэша](https://github.com/dotnet/runtime/blob/1c4755daf8f25f067a360c1dcae0d19df989e4e7/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs#L277). | ||
|
||
В `FrozenDictionary` хэш-коды всех ключей известны заранее. Это позволяет выбрать такое количество бакетов, при котором число коллизий не превышает допустимый порог — на данный момент это 5%. Делается это следующим образом: | ||
|
||
1. Выбирается количество бакетов n из определённого диапазона. Этот диапазон [подобран разработчиками .NET эвристическим способом](https://github.com/dotnet/runtime/blob/1c4755daf8f25f067a360c1dcae0d19df989e4e7/src/libraries/System.Collections.Immutable/src/System/Collections/Frozen/FrozenHashTable.cs#L177). | ||
2. Для каждого хэш-кода рассчитывается остаток от деления на `n` и считается количество одинаковых результатов. | ||
3. Если количество коллизий слишком велико, то `n` увеличивается. | ||
|
||
Вот пример с `bool[]`: | ||
|
||
``` csharp | ||
int[] hashCodes = [/*some hash codes*/]; | ||
var seenBuckets = new bool[bucketsNumber]; | ||
var collisionsCount = 0; | ||
|
||
foreach (var hash in hashCodes) { | ||
var bucketIndex = hash % bucketsCount; | ||
if (seenBuckets[bucketIndex]) { | ||
collisionsCount++; | ||
} | ||
else { | ||
seenBuckets[bucketIndex] = true; | ||
} | ||
} | ||
``` | ||
|
||
Но в .NET сделали иначе и вместо `bool[]` используют `int[]` с битовыми сдвигами. | ||
|
||
``` csharp | ||
int[] hashCodes = [/*some hash codes*/]; | ||
var seenBuckets = new int[bucketsNumber]; | ||
var collisionsCount = 0; | ||
|
||
foreach (var hash in hashCodes) { | ||
var bucketIndex = hash % bucketsCount; | ||
var i = bucketIndex / 32; | ||
var bit = 1 << bucketIndex; | ||
if ((seenBuckets[i] & bit) != 0) { | ||
collisionsCount++; | ||
} | ||
else { | ||
seenBuckets[i] |= bit; | ||
} | ||
} | ||
``` | ||
|
||
Такой подход позволяет значительно уменьшить размер массива (20% – 80%) и время его создания уменьшается в среднем на 70%. В то же время, скорость чтения и записи снижается в среднем на 50% и 198% соответственно (см. графики). | ||
|
||
<img src="{{site.baseurl}}/assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image01.png" alt="content"> | ||
|
||
<img src="{{site.baseurl}}/assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image02.png" alt="content"> | ||
|
||
<img src="{{site.baseurl}}/assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image03.png" alt="content"> | ||
|
||
<img src="{{site.baseurl}}/assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image04.png" alt="content"> | ||
|
||
Получается, использование целых чисел с битовым сдвигом – компромиссное решение. Команда .NET видимо посчитала, что аллоцировать меньше памяти важнее, чем скорость алгоритма. |
Binary file added
BIN
+42.9 KB
assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+40.9 KB
assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+45.6 KB
assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+50.7 KB
assets/2024/09/2024-09-09-frozen-dictionary-collisions-count/image04.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.