Замеры FrozenDictionary против Dictionary, ImmutableDictionary,
ReadOnlyDictionary и switch по строковым константам на .NET 8, 9 и 10.
Четыре машины, BenchmarkDotNet 0.15.8, сборка Release.
Репозиторий к статье «Бенчмаркая FrozenDictionary: заменил Dictionary — получил от ×0,76 до ×1,9».
Во сколько раз FrozenDictionary быстрее Dictionary на .NET 10,
256 поисков за вызов, только попадания.
| Ключей | Формат | №1 Ryzen | №2 i9 | №3 Xeon Silver | №4 Xeon W |
|---|---|---|---|---|---|
| 5 | k7 | ×0,99 | ×0,98 | ×1,21 | ×0,98 |
| 5 | маршрут | ×1,09 | ×0,83 | ×1,01 | ×0,76 |
| 50 | k7 | ×1,06 | ×1,49 | ×1,10 | ×1,60 |
| 50 | маршрут | ×1,55 | ×1,90 | ×1,81 | ×1,80 |
| 500 | k7 | ×1,18 | ×1,58 | ×1,38 | ×1,61 |
| 500 | маршрут | ×1,47 | ×1,80 | ×1,79 | ×1,82 |
Короткий ключ — k7, ключ маршрута — /api/v2/orders/handler_7.
Чтение. На 50 и 500 ключах Frozen быстрее в 1,06–1,61 раза на коротких ключах и в 1,47–1,90 раза на ключах маршрутов с общим префиксом. На пяти ключах смысла в нём нет: на коротких ключах он идёт вровень с Dictionary на трёх машинах из четырёх, на маршрутах проигрывает до ×0,76.
Версия рантайма. У Dictionary между .NET 8, 9 и 10 единого направления нет: разница держится в пределах 9% и на части машин уходит в минус. Frozen за те же версии ускорился на 9–24%.
Куда уходит время. Поиск по ключу int в 2,8–4,3 раза быстрее, чем по
строковому. Хэширование строки занимает 25–44% времени поиска — это оценка
сверху, снятая на более медленном хэше, чем берёт сам Dictionary.
Компаратор, которого Dictionary не знает, отнимает ещё 12–46%.
Промахи. Если ключа в наборе нет, Frozen быстрее Dictionary в 1,2–2,0 раза на ключе той же формы и в 2,7–5,6 раза на ключе другой длины — такой отбрасывается до вычисления хэша.
Switch. На наборе из 50 ключей switch по строковым константам быстрее
Frozen в 2,9–3,6 раза и Dictionary в 4,8–6,7 раза. Длина ключа на это почти
не влияет.
Создание. Frozen строится в 5,0–6,9 раза дольше Dictionary, ImmutableDictionary — в 13–15 раз. ReadOnlyDictionary почти не отличается от Dictionary.
FrozenDictionary подбирает внутреннюю реализацию под конкретный набор
ключей. Проверяется одной строкой, вывод лежит в
Results/frozen_types.txt:
Console.WriteLine(dict.ToFrozenDictionary().GetType().Name);| Ключей | Формат | Реализация |
|---|---|---|
| 5 | k7 | LengthBucketsFrozenDictionary |
| 5 | маршрут | LengthBucketsFrozenDictionary |
| 50 | k7 | OrdinalStringFrozenDictionary_RightJustifiedSubstring |
| 50 | маршрут | OrdinalStringFrozenDictionary_RightJustifiedSubstring |
| 500 | k7 | OrdinalStringFrozenDictionary_Full |
| 500 | маршрут | OrdinalStringFrozenDictionary_RightJustifiedSubstring |
| № | CPU | Ядра / потоки | Частота | ОС |
|---|---|---|---|---|
| 1 | AMD Ryzen 9 5950X | 16 / 32 | 3,4 ГГц, до 4,9 в турбо | Windows 10 1809 |
| 2 | Intel Core i9-10900KF | 10 / 20 | 3,7 ГГц, до 5,3 в турбо | Windows 10 22H2 |
| 3 | 2 × Intel Xeon Silver 4314 | 2×16 / 64 | 2,4 ГГц, до 3,4 в турбо | Windows Server 2022 |
| 4 | Intel Xeon W-2255 | 10 / 20 | 3,7 ГГц, до 4,5 в турбо | Windows Server 2022 |
Патч-версии рантайма на машинах разные, поэтому сравнивать имеет смысл столбцы внутри одной машины, а не числа разных машин между собой.
Нужен .NET SDK 8, 9 и 10 — прогон многоцелевой.
dotnet run -c Release -f net10.0 все замеры
dotnet run -c Release -f net10.0 -- types реализации FrozenDictionary
nopgo.bat контрольный прогон без PGO
Disasm\snap.bat листинги машинного кода
Отчёты появятся в BenchmarkDotNet.Artifacts\results, листинги — рядом
со snap.bat.
Словари на 5, 50 и 500 записей со строковыми ключами. За один вызов
бенчмарка выполняется 256 поисков через TryGetValue. Ключи для поиска
выбираются заранее генератором случайных чисел с фиксированным начальным
значением, порядок перемешан. Компаратор нигде не задаётся: для строковых
ключей Dictionary подставляет свой.
GlobalSetup сверяет суммы всех способов между собой и падает при
расхождении — так проверяется, что все варианты делают одну и ту же работу.
Каждый измеряемый метод лежит в Subjects.cs и помечен NoInlining:
иначе его не найти в дизассемблированном коде.
Subjects.cs все измеряемые методы
Program.cs запуск замеров
Enums/ параметры бенчмарков
Diagnostics/ вывод выбранных реализаций FrozenDictionary
Benchmarks/
FrozenLookupBench.cs чтение: 4 способа × 3 размера × hit/miss × 2 формата ключа
FrozenCreateBench.cs сборка тех же четырёх словарей
DictWhyBench.cs из чего складывается поиск по строковому ключу
DictNoPgoBench.cs контрольный прогон с выключенным PGO
FrozenMissBench.cs два вида промаха: та же форма и другая длина
SwitchRouteBench.cs switch на ключах маршрутов против словарей
Results/
Comp_1..Comp_4/ отчёты по каждой машине: csv, md, html
frozen_types.txt вывод команды types
Disasm/
Program.cs прогрев для снятия листингов
snap.bat снятие листингов
Listings_Comp_1..4/ листинги по машинам, .NET 8/9/10/11