Skip to content

Repository files navigation

FrozenProof

Замеры 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

Ссылки

About

FrozenDictionary против Dictionary, ImmutableDictionary, ReadOnlyDictionary и switch: замеры на .NET 8/9/10, четыре машины, дизасм и исходники рантайма

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages