Как ошибки в бенчмарке могут привести к неправильным выводам
Видел я однажды пост в LinkedIn, заголовок которого утверждал, что .NET 9 медленнее, чем .NET 8. Сильное заявление. Проверять я его конечно буду. Ведь я сам большой любитель замеров производительности. Перейдём сразу к тому, что не так с бенчмарком.
❌ Методы не возвращают результат
Современные компиляторы умные. Они могут понять, когда выполняемый код не влияет на результат программы, и просто удаляют его. Такая оптимизация называется dead-code elimination (DCE). В данном случае, метод
✅ Всегда возвращайте результат из методов
Исправленный вариант может выглядеть так. Нам неважно, что будет в переменной sum. Главное, что её использование предотвращает DCE и не оказывает значительного влияния на результаты бенчмарка.
❌ Слепое сравнивание foreach
Как мы знаем,
- foreach с массивом будет преобразован в while c индексатором;
- foreach со списком – в while с List<int>.Enumerator;
- foreach с ICollection<int> – в while с IEnumerator<int>.
✅ Сравнивать производительность foreach нужно с разными типами
Поскольку
❌ Передача метода в виде параметра
При передаче метода в виде параметра создаётся делегат. В нашем примере –
✅ При передаче метода в виде параметра нужно заранее создать экземпляр делегата
Правильнее было бы проинициализировать делегат в методе
Выводы
Теперь посмотрим на результаты исправленного бенчмарка. Bad – изначальный бенчмарк, Better – после исправления.
В бенчмарке Bad, который я запустил на своём ноутбуке, метод
В бенчмарке Better, метод
В целом, результаты во всех трёх версиях .NET кажутся плюс-минус одинаковыми. Поэтому, я бы не стал заявлять о том, что .NET 9 медленнее .NET 8.
Видел я однажды пост в LinkedIn, заголовок которого утверждал, что .NET 9 медленнее, чем .NET 8. Сильное заявление. Проверять я его конечно буду. Ведь я сам большой любитель замеров производительности. Перейдём сразу к тому, что не так с бенчмарком.
❌ Методы не возвращают результат
Современные компиляторы умные. Они могут понять, когда выполняемый код не влияет на результат программы, и просто удаляют его. Такая оптимизация называется dead-code elimination (DCE). В данном случае, метод
DoSomeThing фактически не делает ничего, поэтому компилятор удаляет его в методе For. Но в методе ForEach_Linq такое сделать нельзя, т.к. в нём создаётся делегат. В результате получается сравнение методов с разным поведением (рисунок 2).✅ Всегда возвращайте результат из методов
Исправленный вариант может выглядеть так. Нам неважно, что будет в переменной sum. Главное, что её использование предотвращает DCE и не оказывает значительного влияния на результаты бенчмарка.
❌ Слепое сравнивание foreach
Как мы знаем,
foreach предназначен для итерации по коллекциям, которые возвращают Enumerator. Но даже если коллекция возвращает Enumerator, это не гарантирует, что он будет использоваться. Рассмотрим пример на рисунке 3:- foreach с массивом будет преобразован в while c индексатором;
- foreach со списком – в while с List<int>.Enumerator;
- foreach с ICollection<int> – в while с IEnumerator<int>.
✅ Сравнивать производительность foreach нужно с разными типами
Поскольку
foreach компилируется в различный код в зависимости от коллекции, производительность может значительно отличаться. Поэтому важно сравнивать каждый тип отдельно. Не исключаю, что эта деталь реализации может измениться в будущем.❌ Передача метода в виде параметра
При передаче метода в виде параметра создаётся делегат. В нашем примере –
Action<int> (рисунок 4). Делегат, как известно ссылочный тип, то есть его создание – аллокация памяти, которая влияет на результаты бечмарка.✅ При передаче метода в виде параметра нужно заранее создать экземпляр делегата
Правильнее было бы проинициализировать делегат в методе
GlobalSetup, который выполняется перед бенчмарками.Выводы
Теперь посмотрим на результаты исправленного бенчмарка. Bad – изначальный бенчмарк, Better – после исправления.
В бенчмарке Bad, который я запустил на своём ноутбуке, метод
ForEach_LinqParallel отработал на .NET 9 чуть быстрее, чем на .NET 8. То есть та разница в, о которой говорил автор, была в рамках погрешности.В бенчмарке Better, метод
ForEach_LinqParallel отработал на .NET 9 медленнее чем на .NET 8 на 90 мкс. Такую разницу я бы тоже отнёс к погрешности и не стал акцентировать внимание.В целом, результаты во всех трёх версиях .NET кажутся плюс-минус одинаковыми. Поэтому, я бы не стал заявлять о том, что .NET 9 медленнее .NET 8.
👍9
Soft Layoffs, или как сократить персонал с минимальными потерями для бизнеса
16 сентября CEO Amazon объявил, что с 2025 года все сотрудники компании будут работать 5 дней в неделю из офиса:
Amazon – не единственная компания, которая изменила подход к удалённой работе. Например, 18 сентября появилась новость, что теперь работники Ubisoft обязаны работать 3 дн в неделю из офиса. В июле Salesforce объявляла о необходимости работать в офиса 4-5 дней в неделю. Таких примеров можно найти множество.
Почти все такие новости подаются как забота об уникальной корпоративной культуре, но за этим скрывается решение проблемы с раздутым штатом и стремление сохранить прибыль. Есть мнение, что во время пандемии было нанято много новых сотрудников для удовлетворения возросшего спроса на IT-услуги, но с её окончанием столько работников уже не требуется. Встал вопрос, как с ними расстаться. Один из таких методов — Soft Layoffs, или мягкие увольнения.
Мягкие увольнения происходят уже довольно продолжительное время. В феврале этого года я публиковал перевод истории с Reddit, где IT-специалист из США рассказывал о том, как его вынуждают вернуться в офис, хотя он не хотел этого. Суть Soft Layoffs заключается в том, чтобы создать условия, при которых сотрудник уволится сам, что позволяет компании сократить расходы.
Способы мягких увольнений
Помимо введения обязательной работы в офисе, существуют и другие способы вынудить уволиться по-собственному:
- Заморозка найма с распределением новой нагрузки на существующих работников. Это приводит к переработкам, выгоранию и поиску новой работы.
- Урезание бенефитов: отмена корпоративов, ухудшение условий или полная отмена ДМС, замена зернового кофе на 3 в 1, а печенек на армейские галеты.
- Отмена индексации зарплат, квартальных и годовых премий.
Это основные методы, которые пришли мне в голову, но, вероятно, существуют и другие.
Преимущества для работодателей
Термин Soft Layoffs пришёл с Запада и я не знаю подробностей их трудового законодательства. Но наверняка там, как и в России, сокращение — сложная для работодателя процедура со множеством нюансов и ограничений. Проще договориться об увольнении по соглашению сторон или, если работодатель редиска, то вынудить уволиться по собственному желанию. Это позволяет:
- Разгрузить административный персонал, который не будет заниматься подготовкой документов для сокращения по ТК.
- Сократить расходы на выходные пособия, ведь при увольнении по собственному желанию они не выплачиваются.
- Избавиться от тех, кого уволить сложно уволить, например, беременных женщин или одиноких родителей с маленькими детьми.
- Минимизировать репутационные потери. «Компания X сокращает 10 000 человек» звучит хуже, чем «Компания X возвращает работников в офис».
Что в таких ситуациях делать работникам?
Работникам всегда нужно заботиться о своих интересах. Интересами работодателя пусть занимается сам работодатель. Конкретные советы, например, закрепление условий работы в коллективном договоре, я упоминал в майском посте про сокращение, под которое попал в 2022 году.
Если с поиском новой работы проблемы, а коллективного договора нет, можно применить стратегию quite quitting. Это когда сотрудник работает ровно столько, сколько требуют его должностные обязанности – ни больше, ни меньше. Он перестаёт перерабатывать, не стремится к повышению, не берёт на себя дополнительные задачи и не проявляет инициативу.
16 сентября CEO Amazon объявил, что с 2025 года все сотрудники компании будут работать 5 дней в неделю из офиса:
Our culture is unique <...>. But, keeping your culture strong is not a birthright. You have to work at it all the time. <...> the number of people we’ve hired the last 6-8 years to pursue these endeavors, it’s pretty unusual—and will stretch even the strongest of cultures.
<...> some of our teammates may have set up their personal lives in such a way that returning to the office consistently five days per week will require some adjustments. To help ensure a smooth transition, we’re going to make this new expectation active on January 2, 2025.
Amazon – не единственная компания, которая изменила подход к удалённой работе. Например, 18 сентября появилась новость, что теперь работники Ubisoft обязаны работать 3 дн в неделю из офиса. В июле Salesforce объявляла о необходимости работать в офиса 4-5 дней в неделю. Таких примеров можно найти множество.
Почти все такие новости подаются как забота об уникальной корпоративной культуре, но за этим скрывается решение проблемы с раздутым штатом и стремление сохранить прибыль. Есть мнение, что во время пандемии было нанято много новых сотрудников для удовлетворения возросшего спроса на IT-услуги, но с её окончанием столько работников уже не требуется. Встал вопрос, как с ними расстаться. Один из таких методов — Soft Layoffs, или мягкие увольнения.
Мягкие увольнения происходят уже довольно продолжительное время. В феврале этого года я публиковал перевод истории с Reddit, где IT-специалист из США рассказывал о том, как его вынуждают вернуться в офис, хотя он не хотел этого. Суть Soft Layoffs заключается в том, чтобы создать условия, при которых сотрудник уволится сам, что позволяет компании сократить расходы.
Способы мягких увольнений
Помимо введения обязательной работы в офисе, существуют и другие способы вынудить уволиться по-собственному:
- Заморозка найма с распределением новой нагрузки на существующих работников. Это приводит к переработкам, выгоранию и поиску новой работы.
- Урезание бенефитов: отмена корпоративов, ухудшение условий или полная отмена ДМС, замена зернового кофе на 3 в 1, а печенек на армейские галеты.
- Отмена индексации зарплат, квартальных и годовых премий.
Это основные методы, которые пришли мне в голову, но, вероятно, существуют и другие.
Преимущества для работодателей
Термин Soft Layoffs пришёл с Запада и я не знаю подробностей их трудового законодательства. Но наверняка там, как и в России, сокращение — сложная для работодателя процедура со множеством нюансов и ограничений. Проще договориться об увольнении по соглашению сторон или, если работодатель редиска, то вынудить уволиться по собственному желанию. Это позволяет:
- Разгрузить административный персонал, который не будет заниматься подготовкой документов для сокращения по ТК.
- Сократить расходы на выходные пособия, ведь при увольнении по собственному желанию они не выплачиваются.
- Избавиться от тех, кого уволить сложно уволить, например, беременных женщин или одиноких родителей с маленькими детьми.
- Минимизировать репутационные потери. «Компания X сокращает 10 000 человек» звучит хуже, чем «Компания X возвращает работников в офис».
Что в таких ситуациях делать работникам?
Работникам всегда нужно заботиться о своих интересах. Интересами работодателя пусть занимается сам работодатель. Конкретные советы, например, закрепление условий работы в коллективном договоре, я упоминал в майском посте про сокращение, под которое попал в 2022 году.
Если с поиском новой работы проблемы, а коллективного договора нет, можно применить стратегию quite quitting. Это когда сотрудник работает ровно столько, сколько требуют его должностные обязанности – ни больше, ни меньше. Он перестаёт перерабатывать, не стремится к повышению, не берёт на себя дополнительные задачи и не проявляет инициативу.
👍9
Как данные влияют на производительность
Хочу рассказать о том, насколько важен способ инициализации данных для достижения максимальной производительности кода. Рассмотрим следующий синтетический пример (см. рисунок):
- Массивы t1 и t2 содержат информацию о транзакциях.
- Два метода, Setup1 и Setup2, инициализируют массивы t1 и t2 соответственно: Setup1 – двумя циклами for, а Setup2 – одним.
- Метод Sum позволяет вычислить сумму транзакций в массивах t1 или t2.
Вопрос: при каком способе инициализации Sum(t1) + Sum(t2) выполнится быстрее? Голосуйте в опросе ниже. Правильный ответ с результатами бенчмарка и объяснением опубликую позднее.
Хочу рассказать о том, насколько важен способ инициализации данных для достижения максимальной производительности кода. Рассмотрим следующий синтетический пример (см. рисунок):
- Массивы t1 и t2 содержат информацию о транзакциях.
- Два метода, Setup1 и Setup2, инициализируют массивы t1 и t2 соответственно: Setup1 – двумя циклами for, а Setup2 – одним.
- Метод Sum позволяет вычислить сумму транзакций в массивах t1 или t2.
Вопрос: при каком способе инициализации Sum(t1) + Sum(t2) выполнится быстрее? Голосуйте в опросе ниже. Правильный ответ с результатами бенчмарка и объяснением опубликую позднее.
👍3
При каком способе инициализации Sum(t1) + Sum(t2) выполнится быстрее?
Final Results
10%
Результат будет одинаковый
38%
Setup2 — один цикл for
52%
Setup1 — два цикла for
Как данные влияют на производительность. Ответ и объяснение.
Правильный ответ: Sum(t1) + Sum(t2) рассчитывается быстрее, если массивы инициализировать двумя циклами for.
Почему так? Всё дело в кэше данных процессора. Я уже писал пост на эту тему, но тогда рассматривал два разных алгоритма для обхода одной и той же матрицы. В данном случае алгоритм не меняется, но данные инициализируются по-разному.
Если массивы t1 и t2 инициализировать одним циклом for, то в памяти это выглядит примерно так:
При расчёте, например, Sum(t1), в самом начале происходит обращение к элементу t1[0]. Копирование данных из памяти в кэш происходит блоками по 64 байта — это размер линии кэша большинства современных процессоров. Из-за этого в кэш попадают элементы массива t2, хотя методу Sum нужны элементы только одного массива. В результате увеличивается количество промахов кэша и количество дополнительных обращений к памяти. Алгоритм выполняется на 72% медленнее.
Если массивы t1 и t2 инициализировать двумя циклами for, то локальность данных более высокая, что снижает количество промахов кэша:
Код бенчмарка и результаты тут.
Правильный ответ: Sum(t1) + Sum(t2) рассчитывается быстрее, если массивы инициализировать двумя циклами for.
Почему так? Всё дело в кэше данных процессора. Я уже писал пост на эту тему, но тогда рассматривал два разных алгоритма для обхода одной и той же матрицы. В данном случае алгоритм не меняется, но данные инициализируются по-разному.
Если массивы t1 и t2 инициализировать одним циклом for, то в памяти это выглядит примерно так:
t1[0], t2[0], t1[1], t2[1], t1[2], t2[3], ...
При расчёте, например, Sum(t1), в самом начале происходит обращение к элементу t1[0]. Копирование данных из памяти в кэш происходит блоками по 64 байта — это размер линии кэша большинства современных процессоров. Из-за этого в кэш попадают элементы массива t2, хотя методу Sum нужны элементы только одного массива. В результате увеличивается количество промахов кэша и количество дополнительных обращений к памяти. Алгоритм выполняется на 72% медленнее.
Если массивы t1 и t2 инициализировать двумя циклами for, то локальность данных более высокая, что снижает количество промахов кэша:
t1[0], t1[1], t1[2], t1[3], ...
t2[0], t2[1], t2[2], t2[3], ...
Код бенчмарка и результаты тут.
👍8
Увольнения в ABBYY
Сегодня появилась новость об увольнениях айтишников с российскими паспортами в ABBYY. Это подтвердила моя коллега, с которой мы в прошлом работали в одной команде. По её словам, в конце прошлой недели им назначили митинги. Из приглашённых были только россияне, кроме HR и топ-менеджмента. Доступы сразу же забрали, и сейчас они находятся в оплачиваемом отпуске на два месяца.
По её же словам, разговоры об увольнениях велись уже на протяжении полугода. Поводом послужили попытки заменить QA-специалистов на работников из Индии. Да, в очередной раз корпораты «отбангалорили» сотрудников.
Я уже рассказывал в мае про то, как в 2022 году мою команду заменили командой из Индии. В том же посте я рассказал, как обычным работникам можно подготовиться к таким внезапным поворотам. Ещё раз повторю тезисы:
1. Развивайте навыки, так как при внезапном увольнении придётся конкурировать за рабочее место.
2. Развивайте нетворкинг, общайтесь со своими коллегами. В случае несправедливого увольнения проще защищать свои права коллективно. Связи с коллегами из других компаний могут помочь быстрее найти новую работу.
3. Договоритесь с работодателем о выходном пособии при сокращении и закрепите договорённости в коллективном договоре.
Сегодня появилась новость об увольнениях айтишников с российскими паспортами в ABBYY. Это подтвердила моя коллега, с которой мы в прошлом работали в одной команде. По её словам, в конце прошлой недели им назначили митинги. Из приглашённых были только россияне, кроме HR и топ-менеджмента. Доступы сразу же забрали, и сейчас они находятся в оплачиваемом отпуске на два месяца.
По её же словам, разговоры об увольнениях велись уже на протяжении полугода. Поводом послужили попытки заменить QA-специалистов на работников из Индии. Да, в очередной раз корпораты «отбангалорили» сотрудников.
Я уже рассказывал в мае про то, как в 2022 году мою команду заменили командой из Индии. В том же посте я рассказал, как обычным работникам можно подготовиться к таким внезапным поворотам. Ещё раз повторю тезисы:
1. Развивайте навыки, так как при внезапном увольнении придётся конкурировать за рабочее место.
2. Развивайте нетворкинг, общайтесь со своими коллегами. В случае несправедливого увольнения проще защищать свои права коллективно. Связи с коллегами из других компаний могут помочь быстрее найти новую работу.
3. Договоритесь с работодателем о выходном пособии при сокращении и закрепите договорённости в коллективном договоре.
👍9
Cравнение string и Span<T>
Решил обновить старую статью о сравнении string и Span<T>, так как в ней были допущены ошибки в бенчмарках. Ссылку прикладывать не буду по этой же причине. На этот раз планирую провести замеры во всех методах, которые есть и в string, и в MemoryExtensions, и подготовить что-то вроде cheetsheet. Это будет таблица, к которой можно обратиться, чтобы понять, а стоит ли заморачиваться со Span<T> в конкретном случае или же производительность будет одинаковой.
Но перед публикацией хочу понять, насколько аудитория знакома со Span<T> и его преимуществами. Если выяснится, что большинство плохо знакомы с этой структурой, я опубликую список источников, где можно восполнить пробелы в знаниях.
Решил обновить старую статью о сравнении string и Span<T>, так как в ней были допущены ошибки в бенчмарках. Ссылку прикладывать не буду по этой же причине. На этот раз планирую провести замеры во всех методах, которые есть и в string, и в MemoryExtensions, и подготовить что-то вроде cheetsheet. Это будет таблица, к которой можно обратиться, чтобы понять, а стоит ли заморачиваться со Span<T> в конкретном случае или же производительность будет одинаковой.
Но перед публикацией хочу понять, насколько аудитория знакома со Span<T> и его преимуществами. Если выяснится, что большинство плохо знакомы с этой структурой, я опубликую список источников, где можно восполнить пробелы в знаниях.
👍11
Ваш опыт использования Span<T> в C#?
Final Results
14%
Что это такое? 😳
37%
Слышал(а), но не использовал(а)
28%
Использовал(а) несколько раз
21%
Использую регулярно
3 полезных ресурса про Span<T>
По итогам опроса, 51% не использовали Span в работе. Попробую помочь это исправить – начните со следующих ресурсов:
📺 Adam SITNIK: Spanification @ Update Conference Prague 2018.
Доклад Адама Ситника, полностью посвящённый Span<T>. Адам Ситник — разработчик в Microsoft, отвечающий за производительность .NET и один из основных мейнтейнеров библиотеки BenchmarkDotNet.
📺 High-performance code design patterns in C#. Konrad Kokosa .NET Fest 2019.
Доклад Конрада Кокосы о паттернах для высокой производительности. На 16:15 он рассказывает про Span<T>.
📕 Pro .NET Memory Management.
Книга Конрада Кокосы об управлении памятью в .NET. Есть на русском, но перевод мне совсем не нравится. Рекомендую читать оригинал. Глава 14 начинается с рассказа про Span<T>. Впрочем, лучше прочесть всю книгу целиком.
На следующей неделе опубликую первую часть результатов сравнения string и Span.
По итогам опроса, 51% не использовали Span в работе. Попробую помочь это исправить – начните со следующих ресурсов:
Доклад Адама Ситника, полностью посвящённый Span<T>. Адам Ситник — разработчик в Microsoft, отвечающий за производительность .NET и один из основных мейнтейнеров библиотеки BenchmarkDotNet.
Доклад Конрада Кокосы о паттернах для высокой производительности. На 16:15 он рассказывает про Span<T>.
📕 Pro .NET Memory Management.
Книга Конрада Кокосы об управлении памятью в .NET. Есть на русском, но перевод мне совсем не нравится. Рекомендую читать оригинал. Глава 14 начинается с рассказа про Span<T>. Впрочем, лучше прочесть всю книгу целиком.
На следующей неделе опубликую первую часть результатов сравнения string и Span.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
Сравнение string и Span: поиск символов и подстрок
Публикую первую часть результатов сравнения методов string и Span. Начнём с методов для поиска символов и подстрок. Замеры проводились на массиве из 100 000 строк. Я постарался скомпоновать данные в виде диаграмм, но поскольку я сравнивал перегрузки с различными значениями StringComparison, то данных получилось очень много. Сырые результаты и бенчмарки можно найти здесь. Основные выводы, которые можно сделать на основе полученных данных ниже.
Contains
🟢 Методы string.Contains(char value), string.Contains(string value), ReadOnlySpan<char>(char value) работают одинаково быстро.
🟢 Методы Contains(char value, StringComparison comparisonType) и Contains(string value, StringComparison comparisonType) работают быстро при использовании Ordinal, OrdinalIgnoreCase, InvariantCulture и InvariantCultureIgnoreCase.
🟠 Использование CurrentCulture и CurrentCultureIgnoreCase медленее в 250 – 330 раз.
StartsWith
🟢 Методы StartsWith(string value, StringComparison comparisonType) работают быстро при использовании Ordinal, OrdinalIgnoreCase, InvariantCulture и InvariantCultureIgnoreCase.
🟠 Использование CurrentCulture и CurrentCultureIgnoreCase медленее примерно в 100 раз.
🟠 Метод string.StartsWith(string value) под капотом использует перегрузку с параметром CurrentCulture.
EndsWith
🟢 Методы EndsWith(string value, StringComparison comparisonType) работают быстро при использовании Ordinal, OrdinalIgnoreCase, InvariantCulture и InvariantCultureIgnoreCase.
🟠 Использование CurrentCulture и CurrentCultureIgnoreCase медленее примерно в 100 раз.
🟠 Метод string.EndsWith(string value) под капотом использует перегрузку с параметром CurrentCulture.
IndexOf
🟢 Метод IndexOf(char value) работает одинаково быстро как в string, так и в Span.
🟢 Метод IndexOf(string value) работает быстро только в ReadOnlySpan.
🟢 Методы IndexOf(char value, StringComparison comparisonType) и IndexOf(string value, StringComparison comparisonType) работают быстро при использовании Ordinal, OrdinalIgnoreCase, InvariantCulture и InvariantCultureIgnoreCase.
🟠 Использование CurrentCulture и CurrentCultureIgnoreCase медленее примерно в 260 раз.
🟠 Метод string.IndexOf(string value) под капотом использует перегрузку с параметром CurrentCulture.
LastIndexOf
🟢 Метод LastIndexOf(char value) работает одинаково быстро как в string, так и в Span.
🟢 Методы LastIndexOf(char value, StringComparison comparisonType) и LastIndexOf(string value, StringComparison comparisonType) работают быстро при использовании Ordinal, OrdinalIgnoreCase, InvariantCulture и InvariantCultureIgnoreCase.
🟠 Использование CurrentCulture и CurrentCultureIgnoreCase медленее примерно в 260-370 раз.
🟠 string.LastIndexOf(string value) под капотом этот метод использует перегрузку с параметром CurrentCulture.
IndexOfAny и LastIndexOfAny
🟢 Все методы работают одинаково быстро.
Публикую первую часть результатов сравнения методов string и Span. Начнём с методов для поиска символов и подстрок. Замеры проводились на массиве из 100 000 строк. Я постарался скомпоновать данные в виде диаграмм, но поскольку я сравнивал перегрузки с различными значениями StringComparison, то данных получилось очень много. Сырые результаты и бенчмарки можно найти здесь. Основные выводы, которые можно сделать на основе полученных данных ниже.
Contains
StartsWith
EndsWith
IndexOf
LastIndexOf
IndexOfAny и LastIndexOfAny
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍12
FrozenDictionary Under the Hood: How Fast Is It Compared to Dictionary and Why
I’m happy to share the translation of my article about a new type of generic collections in C#: FrozenDictionary. The main feature of this dictionary is that it’s immutable, but allows reading the data faster comparing to a standard Dictionary.
I split the results on the cover by a reason: the algorithms used in FrozenDictionary are highly depended on key type, the size of the array or even the number of the string keys with the same length.
Curious to learn more? Check out the article!
I’m happy to share the translation of my article about a new type of generic collections in C#: FrozenDictionary. The main feature of this dictionary is that it’s immutable, but allows reading the data faster comparing to a standard Dictionary.
I split the results on the cover by a reason: the algorithms used in FrozenDictionary are highly depended on key type, the size of the array or even the number of the string keys with the same length.
Curious to learn more? Check out the article!
alexeyfv
FrozenDictionary under the hood: how fast is it comparing to Dictionary and why
With .NET 8 release, C# developers received a new type of generic collections – FrozenDictionary. The main feature of this dictionary is that it’s immutable, but allows reading the data faster comparing to a plain Dictionary. I split the results on the cover…
👍6
English version is below.
Продолжаю сравнивать методы string и ReadOnlySpan<char>.
По результатам бенчмарка, методы Trim, TrimStart, TrimEnd в ReadOnlySpan<char> в среднем на 40% быстрее, чем в string. Ничего удивительного нет, т.к. использование этих же методов в string приводит к аллокациям памяти.
Метод string.CompareTo во всех случаях быстрее, правда незначительно. При использовании StringComparison.Ordinal эта разница достигает 88%.
Метод Equals в ReadOnlySpan<char> демонстрирует сопоставимую производительность с string при использовании CurrentCulture, InvariantCulture и других типах сравнения. Однако при использовании OrdinalIgnoreCase string быстрее ~25%.
String and Span comparison. Part 2. Trim, TrimStart, TrimEnd, CompareTo, Equals
I continue to compare the string and ReadOnlySpan<char> methods.
According to the benchmark results, Trim, TrimStart, and TrimEnd methods in ReadOnlySpan<char> are, on average, 40% faster than in string. This is not surprising, because using these methods in string causes memory allocations.
CompareTo is faster in string in all cases, although only slightly. But when using StringComparison.Ordinal, the difference reaches 88%.
Equals in ReadOnlySpan<char> shows comparable performance to string when using all comparison types except OrdinalIgnoreCase. In this case string is ~25% faster.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
English version is below.
Продолжаю сравнивать методы string и ReadOnlySpan<char>.
Метод Split работает на 46% – 64% быстрее в ReadOnlySpan<char>. Использование метода MemoryExtensions.Split позволяет работать со структурой Range, массив которых можно создать в стеке:
Span<Range> ranges = stackalloc Range[str.Length];
str.AsSpan().Split(ranges, separator);
Методы ToLower и ToUpper также работают на 43% – 45% быстрее в ReadOnlySpan<char>. И опять же, это преимущество достигается за счёт отсутствия аллокаций в куче:
Span<char> destination = stackalloc char[str.Length];
str.AsSpan().ToLower(destination, CultureInfo.InvariantCulture);
Производительность метода CopyTo практически одинаковая — разница составляет всего 3% в пользу ReadOnlySpan. Однако важно учитывать, что массив, в который будет скопирована строка, должен быть заранее проинициализирован. Это можно сделать как в стеке с помощью stackalloc, так и в куче с помощью оператора new, что может влиять на производительность:
// Вариант 1
Span<char> destination = new char[str.Length];
str.CopyTo(destination);
// Вариант 2
Span<char> destination = stackalloc char[str.Length];
str.CopyTo(destination);
Метод Replace в ReadOnlySpan<char> работает на 75% быстрее, чем в string. Это очевидно связано с отсутствием аллокаций при использовании ReadOnlySpan — результат замены сохраняется в отдельный Span:
Span<char> destination = stackalloc char[str.Length];
str.AsSpan().Replace(destination, oldChar, newChar);
I continue to compare the string and ReadOnlySpan<char> methods.
The Split method works 46% – 64% faster with ReadOnlySpan<char>. Using the MemoryExtensions.Split method allows to work with Range. An array of ranges can be created on the stack:
Span<Range> ranges = stackalloc Range[str.Length];
str.AsSpan().Split(ranges, separator);
The ToLower and ToUpper methods are also 43% – 45% faster with ReadOnlySpan<char>. Again, this comes from avoiding allocations on a heap:
Span<char> destination = stackalloc char[str.Length];
str.AsSpan().ToLower(destination, CultureInfo.InvariantCulture);
Method CopyTo has almost the same performance — ReadOnlySpan is only 3% faster. However, it's important to note that the destination array should be pre-initialized. This can be done either on the stack using stackalloc or on the heap using the new operator, which can affect performance:
// Option 1
Span<char> destination = new char[str.Length];
str.CopyTo(destination);
// Option 2
Span<char> destination = stackalloc char[str.Length];
str.CopyTo(destination);
Method Replace in ReadOnlySpan<char> is 75% faster than in string. Again, this is due to the absence of heap allocations when using ReadOnlySpan:
Span<char> destination = stackalloc char[str.Length];
str.AsSpan().Replace(destination, oldChar, newChar);
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10
English is below.
Опытные литкодеры знают, насколько нестабильны результаты бенчмарков решений на LeetCode. Одно и то же решение может показывать результаты с большим разбросом. Видимо, они начали что-то с этим делать и на днях изменили подход к замерам:
«Previously, the runtime calculation included overhead processes, but now we only measure the actual time your code takes to run.»
Вольный перевод: Раньше в замеры попадало время холодного старта приложения, а теперь измеряется только время выполнения кода.
С точки зрения бенчмаркинга, это правильное решение. Время холодного старта может значительно искажать результаты микробенчмарков. Однако, это изменение сломало систему рейтинга существующих решений.
Для тех, кто ориентируется только на Big O нотацию, это не должно создать проблем. Остальным придётся смириться с тем, что их старые результаты станут невалидны. Новые решения могут выполняться всего за несколько миллисекунд, тогда как этот же код ранее выполнялся за 100+ мс. При этом на диаграмме отображаются как новые результаты, так и старые, что приводит к тому, что почти любая новая отправка решения приводит к «Beats 100.00%».
Experienced LeetCoders know how unstable the benchmark results can be on LeetCode. The same solution can show results with a big variation. It seems they started fixing it and recently changed the way they measure performance:
«Previously, the runtime calculation included overhead processes, but now we only measure the actual time your code takes to run.»
From a benchmarking perspective, this is the right decision. Cold start can significantly spoil the results of microbenchmarks. However, this change has broken the rating system for existing solutions.
For those who focus only on Big O notation, this shouldn't create any problems. All others have to accept that their old results became invalid. New solutions may run in just a 1-2 milliseconds, while the same code previously ran in over 100 ms. At the same time, both new and old results are displayed on the diagram, leading to nearly any new submission of a solution being marked as «Beats 100.00%».
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6