Начал готовить материал для новой статьи про производительность FrozenDictionary
Для тех, кто не в курсе, что такое FrozenDictionary, – это новая read-only generic-коллекция, которая появились в .NET 8. Его особенностью является улучшенная производительность на чтение, по сравнению с обычными коллекциями.
Что уже нашёл интересного:
1. FrozenDictionary – это абстрактный класс. Его конкретная реализация зависит от исходного словаря. Например, от типа ключа (значимый тип, string, int) или размера коллекции.
2. Некоторые реализации «замороженного» словаря вообще не содержат хэш-таблицы. Вместо этого для поиска значения используется линейный поиск через цикл for. Видимо, для некоторых случаев это быстрее, чем считать хэш.
3. Частое используется ключевое слово ref и агрессивный инлайн методов. Возвращение по ссылке позволяет избежать копирования значимых типов. А инлайн позволяет избежать оверхеда, связанного с вызовом методов.
В скором времени узнаем, насколько быстро всё это работает.
Для тех, кто не в курсе, что такое FrozenDictionary, – это новая read-only generic-коллекция, которая появились в .NET 8. Его особенностью является улучшенная производительность на чтение, по сравнению с обычными коллекциями.
Что уже нашёл интересного:
1. FrozenDictionary – это абстрактный класс. Его конкретная реализация зависит от исходного словаря. Например, от типа ключа (значимый тип, string, int) или размера коллекции.
2. Некоторые реализации «замороженного» словаря вообще не содержат хэш-таблицы. Вместо этого для поиска значения используется линейный поиск через цикл for. Видимо, для некоторых случаев это быстрее, чем считать хэш.
3. Частое используется ключевое слово ref и агрессивный инлайн методов. Возвращение по ссылке позволяет избежать копирования значимых типов. А инлайн позволяет избежать оверхеда, связанного с вызовом методов.
В скором времени узнаем, насколько быстро всё это работает.
🆒4
FrozenDictionary в C#: насколько он быстрее Dictionary. Часть 1. Значимые типы.
С релизом .NET 8 в арсенале C# разработчиков появился новый тип коллекций – FrozenDictionary. В серии статей рассмотрим, что представляет собой этот словарь и насколько он быстрее.
FrozenDictionary<TKey, TValue> – это абстрактный класс. У этого класса есть множество реализаций, зависящих от типа ключа, размера коллекции, компаратора. Создаются они при помощи extension-метода ToFrozenDictionary. Стратегия выбора подходящей реализации реализована в статическом классе FrozenDictionary, а именно в методе CreateFromDictionary.
В этой части рассмотрим реализации с ключом значимого типа и с компаратором по умолчанию. Для таких словарей предусмотрены 4 реализации FrozenDictionary:
1. Int32FrozenDictionary.
2. ValueTypeDefaultComparerFrozenDictionary.
3. SmallValueTypeComparableFrozenDictionary.
4. SmallValueTypeDefaultComparerFrozenDictionary.
Int32FrozenDictionary
Для словарей, с количеством элементов больше 10 и ключом типа Int32 используется класс Int32FrozenDictionary. Эта, как и большинство других реализаций
Особенность
Благодаря этому, чтение из
ValueTypeDefaultComparerFrozenDictionary
Для словарей, с количеством элементов больше 10 и ключом любого другого значимого типа, кроме Int32 используется
Чтение из ValueTypeDefaultComparerFrozenDictionary может быть до 80% быстрее (рис. 2).
Продолжение тут 👇
С релизом .NET 8 в арсенале C# разработчиков появился новый тип коллекций – FrozenDictionary. В серии статей рассмотрим, что представляет собой этот словарь и насколько он быстрее.
FrozenDictionary<TKey, TValue> – это абстрактный класс. У этого класса есть множество реализаций, зависящих от типа ключа, размера коллекции, компаратора. Создаются они при помощи extension-метода ToFrozenDictionary. Стратегия выбора подходящей реализации реализована в статическом классе FrozenDictionary, а именно в методе CreateFromDictionary.
В этой части рассмотрим реализации с ключом значимого типа и с компаратором по умолчанию. Для таких словарей предусмотрены 4 реализации FrozenDictionary:
1. Int32FrozenDictionary.
2. ValueTypeDefaultComparerFrozenDictionary.
3. SmallValueTypeComparableFrozenDictionary.
4. SmallValueTypeDefaultComparerFrozenDictionary.
Int32FrozenDictionary
Для словарей, с количеством элементов больше 10 и ключом типа Int32 используется класс Int32FrozenDictionary. Эта, как и большинство других реализаций
FrozenDictionary, основаны на структуре FrozenHashTable. Это основа «замороженных» словарей. Подробнее о ней я расскажу в последней статье о FrozenDictionary. Пока же достаточно знать, что эта структура содержит в себе множество оптимизаций, которые позволяют быстро производить чтение.Особенность
Int32FrozenDictionary в том, что когда тип ключа – целое число, то его хэш равен его значению и коллизии в таком словаре не возможны в принципе. Нельзя, например, добавить 2 элемента с ключом 123 – будет выброшено исключение. Значит при чтении можно пропустить расчёт хэша и использовать значение ключа напрямую. А при создании словаря, например, пропустить удаление дублирующихся хэшей.Благодаря этому, чтение из
Int32FrozenDictionary быстрее на 36% – 42% (рис. 1).ValueTypeDefaultComparerFrozenDictionary
Для словарей, с количеством элементов больше 10 и ключом любого другого значимого типа, кроме Int32 используется
ValueTypeDefaultComparerFrozenDictionary. В этом случае коллизии могут быть, поэтому при чтении необходим расчёт хэша ключа. Но реализация всё так же основана на FrozenHashTable.Чтение из ValueTypeDefaultComparerFrozenDictionary может быть до 80% быстрее (рис. 2).
Продолжение тут 👇
🆒3
Начало тут 👆
SmallValueTypeComparableFrozenDictionary и SmallValueTypeDefaultComparerFrozenDictionary
Строго говоря, два этих класса и не хэш-таблицы вовсе. Поиск значения в них осуществляется не через
При этом,
Такое разделение разработчики .NET объясняют тем, что у встроенных типов 100% реализован интерфейс
К примеру, если бы нам нужно было найти значение для ключа 10 в словаре как на рисунке 3, то без сортировки пришлось бы обойти весь массив
Кроме того, поскольку массив ключей
Реализация
Несмотря на все оптимизации в этих двух классах, результаты бенчмарка не выглядят впечатляющими (рис. 4). Даже то небольшое ускорение, которое может дать
На сегодня всё. Код и результаты бенчмарка лежит тут. В следующей части рассмотрим ссылочные типы. Самое интересное там связано со ключами типа
SmallValueTypeComparableFrozenDictionary и SmallValueTypeDefaultComparerFrozenDictionary
Строго говоря, два этих класса и не хэш-таблицы вовсе. Поиск значения в них осуществляется не через
FrozenHashTable, а простым сравнением элементов в цикле for. Поэтому эти реализации используются, когда в исходном словаре не более 10 элементов. При этом,
SmallValueTypeComparableFrozenDictionary применяется, если тип ключа – это встроенный примитивный значимый тип: int, long, double, enum и т.д. Если же тип ключа, к примеру, record struct, то будет использован тип SmallValueTypeDefaultComparerFrozenDictionary. Такое разделение разработчики .NET объясняют тем, что у встроенных типов 100% реализован интерфейс
IComparable и поэтому можно немного оптимизировать поиск, отсортировав массивы ключей и значений при инициализации словаря:// ctor
_keys = source.Keys.ToArray();
_values = source.Values.ToArray();
Array.Sort(_keys, _values);
_max = _keys[_keys.Length - 1];
К примеру, если бы нам нужно было найти значение для ключа 10 в словаре как на рисунке 3, то без сортировки пришлось бы обойти весь массив
_keys и убедиться, что такого значения в словаре нет. При наличии сортировки достаточно же сравнить с максимальным значением. В данном случае – 9.Кроме того, поскольку массив ключей
_keys отсортирован, можно осуществлять поиск пока искомое значение ключа больше текущего значения _keys[i].// GetValueRefOrNullRefCore method
if (Comparer<TKey>.Default
.Compare(key, _max) <= 0)
{
TKey[] keys = _keys;
for (int i = 0; i < keys.Length; i++)
{
int c = Comparer<TKey>
.Default.Compare(key, keys[i]);
if (c <= 0)
{
if (c == 0)
{
return ref _values[i];
}
break;
}
}
}
return ref Unsafe.NullRef<TValue>();
Реализация
SmallValueTypeDefaultComparerFrozenDictionary похожа на предыдущую, с тем лишь отличием, что в ней не используется сортировка. Соответственно, линейный поиск по массиву ключей _keys будет осуществлён всегда.Несмотря на все оптимизации в этих двух классах, результаты бенчмарка не выглядят впечатляющими (рис. 4). Даже то небольшое ускорение, которое может дать
SmallValueTypeDefaultComparerFrozenDictionary – это всего лишь несколько наносекунд.На сегодня всё. Код и результаты бенчмарка лежит тут. В следующей части рассмотрим ссылочные типы. Самое интересное там связано со ключами типа
string.🆒4
readonly, readonly everywhere
Сегодня с коллегой зашёл разговор про ключевое слово readonly. После разговора, решил вспомнить в C# всё, что содержит в себе readonly и написал такого монстра:
Этот код компилируется и работает. Естественно, в C# ещё много чего с readonly, но, наверно, и этого достаточно, чтобы запутать C# разработчика.
Кстати, сможете объяснить что значит readonly в каждом из случаев?
Сегодня с коллегой зашёл разговор про ключевое слово readonly. После разговора, решил вспомнить в C# всё, что содержит в себе readonly и написал такого монстра:
public readonly struct SomeStruct(int val)
{
public string SomeField { get; init; }
= "Hello, World!";
public readonly int SomeProperty = val;
public readonly IReadOnlyCollection<int> SomeCollection = [];
public readonly ReadOnlySpan<char> SomeMethod() => SomeField;
}
Этот код компилируется и работает. Естественно, в C# ещё много чего с readonly, но, наверно, и этого достаточно, чтобы запутать C# разработчика.
Кстати, сможете объяснить что значит readonly в каждом из случаев?
🆒4
– Бобрята, а что это у вас?
– Плотина…
– логи
Год назад я рассказывал о налогах, которые платит работник в России и Сербии. Время обновить информацию, так как с 2025 года айтишникам-мажорам в России придётся платить больше налогов из-за прогрессивной шкалы НДФЛ (рис. 1). Сравним зарплаты условных айтишников трёх уровней:
1. 150 тыс. ₽/мес или 1,8 млн ₽/год.
2. 300 тыс. ₽/мес или 3,6 млн ₽/год.
3. 450 тыс. ₽/мес или 5,4 млн ₽/год.
Ставки 20% и 22% не рассматриваются, так как среднестатистический айтишник не зарабатывает больше 20 млн ₽ в год.
Налог на годовой доход
Сперва работа над ошибками. В прошлый раз забыл рассказать о налоге на годовой доход (рис. 2). В Сербии этим налогом облагается доход, превышающий 3 средние годовые зарплаты. В прошлом году средняя годовая зарплата составляла 1 423 188 динаров или примерно 12 112 €. Следовательно, с доходов свыше 36 337 € нужно было уплатить дополнительный налог.
Особенности этого налога:
1. Если вы моложе 40 лет, планка необлагаемого дохода повышается ещё на 3 средние годовые зарплаты.
2. За каждого несовершеннолетнего члена семьи можно получить 15% вычет, но не более 50% суммарно.
Пока ты молод и с доходом выше среднего, то государство не будет дополнительно обременять налогами – зарабатывай. Но к 40 годам будь добр заведи детей. Тогда налогов будешь платить меньше. Видимо тут такая логика? 🤷
Сравнение NET дохода в России и Сербии
Вернёмся к нашим айтишникам. Повторяться с расчётами не будем, а воспользуемся калькуляторами для расчётов налогов в Сербии и РФ. Подробная математика есть в предыдущем посте. Результаты расчётов на рис. 3.
Разработчику в Сербии с зарплатой эквивалентной 450 тыс. ₽/мес также пришлось бы уплатить налог на годовой доход, при условии, что ему минимум 40 лет. При средней ежемесячной зарплате за 1 квартал 2024 года (131 893 динар), 3 средние годовые зарплаты составят 4 748 148 динар или 40 410 €. Значит, ему придётся заплатить ещё почти 1 700 € налогов.
Таким образом, с точки зрения налогов, быть работником в России всё ещё выгоднее – налоги одни из самых низких.
– Плотина…
– логи
Год назад я рассказывал о налогах, которые платит работник в России и Сербии. Время обновить информацию, так как с 2025 года айтишникам-мажорам в России придётся платить больше налогов из-за прогрессивной шкалы НДФЛ (рис. 1). Сравним зарплаты условных айтишников трёх уровней:
1. 150 тыс. ₽/мес или 1,8 млн ₽/год.
2. 300 тыс. ₽/мес или 3,6 млн ₽/год.
3. 450 тыс. ₽/мес или 5,4 млн ₽/год.
Ставки 20% и 22% не рассматриваются, так как среднестатистический айтишник не зарабатывает больше 20 млн ₽ в год.
Налог на годовой доход
Сперва работа над ошибками. В прошлый раз забыл рассказать о налоге на годовой доход (рис. 2). В Сербии этим налогом облагается доход, превышающий 3 средние годовые зарплаты. В прошлом году средняя годовая зарплата составляла 1 423 188 динаров или примерно 12 112 €. Следовательно, с доходов свыше 36 337 € нужно было уплатить дополнительный налог.
Особенности этого налога:
1. Если вы моложе 40 лет, планка необлагаемого дохода повышается ещё на 3 средние годовые зарплаты.
2. За каждого несовершеннолетнего члена семьи можно получить 15% вычет, но не более 50% суммарно.
Пока ты молод и с доходом выше среднего, то государство не будет дополнительно обременять налогами – зарабатывай. Но к 40 годам будь добр заведи детей. Тогда налогов будешь платить меньше. Видимо тут такая логика? 🤷
Сравнение NET дохода в России и Сербии
Вернёмся к нашим айтишникам. Повторяться с расчётами не будем, а воспользуемся калькуляторами для расчётов налогов в Сербии и РФ. Подробная математика есть в предыдущем посте. Результаты расчётов на рис. 3.
Разработчику в Сербии с зарплатой эквивалентной 450 тыс. ₽/мес также пришлось бы уплатить налог на годовой доход, при условии, что ему минимум 40 лет. При средней ежемесячной зарплате за 1 квартал 2024 года (131 893 динар), 3 средние годовые зарплаты составят 4 748 148 динар или 40 410 €. Значит, ему придётся заплатить ещё почти 1 700 € налогов.
Таким образом, с точки зрения налогов, быть работником в России всё ещё выгоднее – налоги одни из самых низких.
🆒4
This media is not supported in your browser
VIEW IN TELEGRAM
Составил список из 15 задач на LeetCode, решив которые вы постигните дзен в понимании бинарного поиска.
Полезно также прочитать вот это обсуждение. Там вы найдёте:
- объяснение, как определить, что в задаче необходим бинарный поиск;
- как правильно вычислять среднее значение, чтобы избежать переполнения при арифметических операциях.
- примеры решений.
Советую решать задачи в следующем порядке:
🟠 Koko Eating Bananas
🟠 Maximum Candies Allocated to K Children
🟠 Minimum Number of Days to Make m Bouquets
🟠 Capacity To Ship Packages Within D Days
🟠 Minimum Speed to Arrive on Time
🟠 Minimum Limit of Balls in a Bag
🟠 Minimum Time to Repair Cars
🟠 Find the Smallest Divisor Given a Threshold
🟠 Minimized Maximum of Products Distributed to Any Store
🟠 Minimum Time to Complete Trips
🟠 Maximum Tastiness of Candy Basket
🟠 Magnetic Force Between Two Balls
🟠 Sell Diminishing-Valued Colored Balls
🔴 Split Array Largest Sum
🔴 Maximum Running Time of N Computers
🟠 – medium, 🔴 – hard.
Please open Telegram to view this post
VIEW IN TELEGRAM
🆒4
Последние несколько недель занимаюсь автоматизацией PKI на работе. Сделал функционал для Let's Encrypt. Дошёл черёд до GoDaddy. Начал исследовать тему. Обнаружил, что несколько месяцев назад GoDaddy ограничила использование DNS API для пользователей, у которых нет подписки или меньше 10 доменов.
Теперь люди, покупая SSL сертификат минимум за $100 / год, должны ещё доплатить за подписку, чтобы автоматизировать обновление сертификата.
Самое паршивое — это изменение было сделано без каких-либо предупреждений. У людей просто начали отваливаться сервисы из-за отсутствия TLS/SSL. А причины проблемы пользователи узнали только после обращения в техподдержку.
Действительно, exceptional.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🆒4
Это не байт, а выдержка из статьи про средний доход американских домохозяйств основанный на данных свежего опроса ФРС США (см. рис. 1).
Net worth – это все доходы минус все расходы. К доходам относят зарплату, доход от вкладов и сдачи в аренду имущества и т.д. К расходам – арендную плату, ипотечные платежи, платежи по другим кредитам и другие обязательства.
🇺🇸 Стоимость жилья в США
Интересно также отметить стоимость жилья (см. рис. 2 и 3). Люди, которые приобрели жильё в ипотеку или арендовали квартиру после 2022 года, платят на 25% – 65% больше по сравнению с теми, кто успел сделать это раньше. Ничего не напоминает? :)
🇪🇺🇷🇸 Европа и Сербия в тренде
В Европе, и в частности в Сербии, где я сейчас живу, те же самые проблемы – жильё значительно подорожало с тех пор, как сюда релокейтнули десятки тысяч россиян, украинцев и белорусов. Про жильё в Сербии думаю нужно сделать отдельный пост.
🇷🇺 Россия в тренде
В этом плане, Россия тоже движется в рамках мирового тренда. Спасибо льготной ипотеке, из-за которого жильё стало настолько доступным, что с 2020 года подорожало минимум в 2 раза.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🆒6
Предыдущий пост тут.
FrozenDictionary в C#: насколько он быстрее Dictionary. Часть 2. Строки.
Продолжаем изучать, что у FrozenDictionary под капотом. Сегодня начнём говорить о словарях с ключом типа string. Для таких словарей предусмотрены аж 12 реализаций FrozenDictionary. Естественно, рассмотрим только принципиальные отличия.
LengthBucketsFrozenDictionary
При создании «замороженных» словарей с ключом типа string, FrozenDictionary в первую очередь попытается создать класс LengthBucketsFrozenDictionary, поэтому начнём именно с него. LengthBucketsFrozenDictionary оптимизирован для ситуаций, когда ключи имеют разную длину. Достигается это распределением ключей по бакетам (корзинам). Алгоритм напоминает блочную сортировку. Для каждой уникальной длины ключа создаётся бакет вместимостью MaxPerLength = 5 элементов.
💡 Чтобы стало понятнее, разберём пример словаря с 6 элементами (рис. 1). В словаре есть ключи длиной 3, 4 и 5 символов. Следовательно, их можно распределить в 3 бакета:
- бакет для ключей длиной 3: fig;
- бакет для ключей длиной 4: lime и kiwi;
- бакет для ключей длиной 5: apple, grape и lemon.
Поскольку известна минимальная (3) и максимальная (5) длина ключей, нет смысла создавать 3 отдельных бакета. Можно всё хранить в одном массиве _lengthBuckets. В таком случае индекс рассчитывается так: (key.Length - minLength) * MaxPerLength.
🔍 Поиск осуществляется в 3 шага (рис. 2):
1. Определяется бакет в массиве _lengthBuckets.
2. Линейным поиском в бакете определяется индекс искомого ключа в _keys.
3. Возвращается значение.
🚫 У LengthBucketsFrozenDictionary есть 2 ограничения:
1. Количество ключей с одинаковой длиной не должно превышать MaxPerLength (принцип Дирихле).
2. Количество пустых бакетов должно быть < 20%. Иначе реализация становится неэффективна с точки зрения использования памяти.
Если одно из этих условий не выполняется, то будет выбрана другая реализация FrozenDictionary. О ней я расскажу в следующей части.
📊 Результаты бенчмарка показывают, что чтение из LengthBucketsFrozenDictionary может быть до 99% быстрее обычного Dictionary. Но если в словаре количество ключей с одинаковой длиной достигает 5, то производительность небольших словарей (до 100 элементов) может быть хуже (рис. 3).
FrozenDictionary в C#: насколько он быстрее Dictionary. Часть 2. Строки.
Продолжаем изучать, что у FrozenDictionary под капотом. Сегодня начнём говорить о словарях с ключом типа string. Для таких словарей предусмотрены аж 12 реализаций FrozenDictionary. Естественно, рассмотрим только принципиальные отличия.
LengthBucketsFrozenDictionary
При создании «замороженных» словарей с ключом типа string, FrozenDictionary в первую очередь попытается создать класс LengthBucketsFrozenDictionary, поэтому начнём именно с него. LengthBucketsFrozenDictionary оптимизирован для ситуаций, когда ключи имеют разную длину. Достигается это распределением ключей по бакетам (корзинам). Алгоритм напоминает блочную сортировку. Для каждой уникальной длины ключа создаётся бакет вместимостью MaxPerLength = 5 элементов.
- бакет для ключей длиной 3: fig;
- бакет для ключей длиной 4: lime и kiwi;
- бакет для ключей длиной 5: apple, grape и lemon.
Поскольку известна минимальная (3) и максимальная (5) длина ключей, нет смысла создавать 3 отдельных бакета. Можно всё хранить в одном массиве _lengthBuckets. В таком случае индекс рассчитывается так: (key.Length - minLength) * MaxPerLength.
1. Определяется бакет в массиве _lengthBuckets.
2. Линейным поиском в бакете определяется индекс искомого ключа в _keys.
3. Возвращается значение.
1. Количество ключей с одинаковой длиной не должно превышать MaxPerLength (принцип Дирихле).
2. Количество пустых бакетов должно быть < 20%. Иначе реализация становится неэффективна с точки зрения использования памяти.
Если одно из этих условий не выполняется, то будет выбрана другая реализация FrozenDictionary. О ней я расскажу в следующей части.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🆒3
Уже больше 150 дней в этом году решаю задачи на LeetCode. На данный момент решено 200 задач, из которых 60% – задачи уровня Medium, 35% – Easy, 5% – Hard.
Сейчас я сосредоточен на улучшении своих слабых сторон, таких как dynamic programming, backtracking, bit manipulation, greedy. Поэтому редко принимаю участие в daily challenge. Вместо этого, решаю задачи из готовых списков, разбитых по темам.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🆒3
Предыдущий пост тут.
FrozenDictionary в C#: насколько он быстрее Dictionary. Часть 3. Ещё строки.
Сегодня рассмотрим оставшиеся реализации FrozenDictionary с ключом типа string. Напомню, что у LengthBucketsFrozenDictionary есть ограничения: если невозможно распределить ключи по бакетам, будет использоваться одна из 11 реализаций абстрактного класса OrdinalStringFrozenDictionary.
Не стоит пугаться большого количества этих реализаций. Все они основаны на одном и том же принципе – расчёте хэш-кода строки. Отличия заключаются в выборе оптимального алгоритма в зависимости от наличия не-ASCII символов, заданных правил сравнения строк (StringComparison) и наличия в ключах уникальных подстрок. Что это всё значит рассмотрим далее.
💡 Выбор оптимальной реализации OrdinalStringFrozenDictionary начинается с анализа ключей классом KeyAnalyzer. Очевидно, что чем длиннее строка, тем медленнее выполняется расчёт хэш-кода. Поэтому KeyAnalyzer пытается найти подстроки наименьшей длины, позволяющие однозначно идентифицировать ключ. Для лучшего понимания рассмотрим пример с фруктами: apple, grape, fig, lime, lemon и kiwi.
Сперва KeyAnalyzer анализирует подстроки длиной в 1 символ при левостороннем выравнивании ключей (рис. 1). В нашем примере есть повторяющиеся подстроки. Например, 0-й символ lime и lemon, 1-й символ fig и lime и 2-й символ в lime и lemon. То есть невозможно при левостороннем выравнивании однозначно идентифицировать ключ по одному символу. Поэтому поиск подстроки продолжается при правостороннем выравнивании. В этом случае при использовании 2-го или 1-го символа с конца подстроки будут уникальны. То есть, зная выравнивание, индекс начала и длину подстроки можно однозначно идентифицировать строку рассчитав хэш-код её подстроки.
Если уникальных подстрок длиной в 1 символ нет, то поиск продолжится для подстрок в 2 символа, 3 символа, вплоть до максимальной длины подстроки. Это значение рассчитывается как минимальное между minLength (минимальная длина ключа) и MaxSubstringLengthLimit = 8. Такое ограничение сделано специально, чтобы не анализировать слишком длинные подстроки, так как их использование не даёт прироста в производительности.
Если уникальных подстрок нет вообще, то расчёт хэш-кода будет производиться для всей строки.
🔍 Поиск в словарях, основанных на OrdinalStringFrozenDictionary, происходит следующим образом:
1. Проверяется, находится ли длина ключа в пределах допустимого диапазона. Это нужно для быстрого определения ключей, которые явно не подходят по длине.
2. Рассчитывается хэш-код подстроки и осуществляется поиск в хэш-таблице.
3. В случае коллизии, осуществляется линейный поиск.
📊 В качестве ключей для бенчмарка я использовал GUID. При инициализации FrozenDictionary, KeyAnalyzer выбрал класс OrdinalStringFrozenDictionary_LeftJustifiedSubstring.
По результатам бенчмарка, FrozenDictionary размером до 75 тыс. элементов быстрее обычного Dictionary. Однако при дальнейшем увеличении размера словаря скорость поиска снижается (рис. 2).
Высокая скорость FrozenDictionary обусловлена быстрым расчётом хэш-кода ключей (рис. 3). Алгоритм FrozenDictionary на 90% – 75% быстрее обычного Dictionary.
Падение производительности в словарях размером 75 тыс. элементов и более вызвано возрастающим количеством коллизий хэша при увеличении размера словаря (рис. 4).
Как видно из графиков, алгоритм, используемый в FrozenDictionary, позволяет ускорить расчёт хэш-кода строки, улучшая производительность до 70%. Но в то же время, такой подход негативно сказывается на производительности поиска в относительно больших словарях.
Со словарями с ключом типа string мы закончили. В FrozenDictionary осталось ещё 2 реализации, которые используются для всех остальных случаев, но в них нет ничего примечательного. Их рассмотрим в заключительном посте в серии про FrozenDictionary. Также, как и обещал, в заключительном посте рассмотрим класс FrozenHashTable – основу большинства «замороженных» словарей.
FrozenDictionary в C#: насколько он быстрее Dictionary. Часть 3. Ещё строки.
Сегодня рассмотрим оставшиеся реализации FrozenDictionary с ключом типа string. Напомню, что у LengthBucketsFrozenDictionary есть ограничения: если невозможно распределить ключи по бакетам, будет использоваться одна из 11 реализаций абстрактного класса OrdinalStringFrozenDictionary.
Не стоит пугаться большого количества этих реализаций. Все они основаны на одном и том же принципе – расчёте хэш-кода строки. Отличия заключаются в выборе оптимального алгоритма в зависимости от наличия не-ASCII символов, заданных правил сравнения строк (StringComparison) и наличия в ключах уникальных подстрок. Что это всё значит рассмотрим далее.
Сперва KeyAnalyzer анализирует подстроки длиной в 1 символ при левостороннем выравнивании ключей (рис. 1). В нашем примере есть повторяющиеся подстроки. Например, 0-й символ lime и lemon, 1-й символ fig и lime и 2-й символ в lime и lemon. То есть невозможно при левостороннем выравнивании однозначно идентифицировать ключ по одному символу. Поэтому поиск подстроки продолжается при правостороннем выравнивании. В этом случае при использовании 2-го или 1-го символа с конца подстроки будут уникальны. То есть, зная выравнивание, индекс начала и длину подстроки можно однозначно идентифицировать строку рассчитав хэш-код её подстроки.
Если уникальных подстрок длиной в 1 символ нет, то поиск продолжится для подстрок в 2 символа, 3 символа, вплоть до максимальной длины подстроки. Это значение рассчитывается как минимальное между minLength (минимальная длина ключа) и MaxSubstringLengthLimit = 8. Такое ограничение сделано специально, чтобы не анализировать слишком длинные подстроки, так как их использование не даёт прироста в производительности.
Если уникальных подстрок нет вообще, то расчёт хэш-кода будет производиться для всей строки.
1. Проверяется, находится ли длина ключа в пределах допустимого диапазона. Это нужно для быстрого определения ключей, которые явно не подходят по длине.
2. Рассчитывается хэш-код подстроки и осуществляется поиск в хэш-таблице.
3. В случае коллизии, осуществляется линейный поиск.
По результатам бенчмарка, FrozenDictionary размером до 75 тыс. элементов быстрее обычного Dictionary. Однако при дальнейшем увеличении размера словаря скорость поиска снижается (рис. 2).
Высокая скорость FrozenDictionary обусловлена быстрым расчётом хэш-кода ключей (рис. 3). Алгоритм FrozenDictionary на 90% – 75% быстрее обычного Dictionary.
Падение производительности в словарях размером 75 тыс. элементов и более вызвано возрастающим количеством коллизий хэша при увеличении размера словаря (рис. 4).
Как видно из графиков, алгоритм, используемый в FrozenDictionary, позволяет ускорить расчёт хэш-кода строки, улучшая производительность до 70%. Но в то же время, такой подход негативно сказывается на производительности поиска в относительно больших словарях.
Со словарями с ключом типа string мы закончили. В FrozenDictionary осталось ещё 2 реализации, которые используются для всех остальных случаев, но в них нет ничего примечательного. Их рассмотрим в заключительном посте в серии про FrozenDictionary. Также, как и обещал, в заключительном посте рассмотрим класс FrozenHashTable – основу большинства «замороженных» словарей.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🆒3