Как объективно сравнить скорость выполнения C#, C++, Java и Go

· · Бенчмаркинг, Производительность, C#, C++, Java, Go

«Похоже, C++ быстрый» «Go лёгкий в реальной эксплуатации» «Java становится довольно быстрой при долгой работе» «C# тоже неожиданно силён благодаря JIT в .NET»

Подобные разговоры возникают постоянно. Но хуже всего здесь то, что чаще всего и происходит: брать цифры, которые разные люди получили в разных окружениях, выстраивать их в ряд и на этом основании делать вывод о превосходстве одного языка над другим.

C# и Java сильно подвержены влиянию JIT и warm-up, тогда как C++ и Go обычно скомпилированы заранее. Отличаются и наличие, и особенности сборщика мусора (GC). Разница в реализации стандартных и сторонних библиотек тоже заметно влияет на результат. А ещё даже на одной и той же машине результаты легко сдвигаются из-за настроек электропитания, нагрева, фоновых процессов и перекоса во входных данных. Мир довольно приземлённый и неопрятный.

В этой статье мы систематизируем способы измерения, позволяющие сравнить C# / C++ / Java / Go максимально объективно. Если сразу дать вывод: важнее всего не пытаться определить «какой язык самый быстрый» одним-единственным числом.

Главная тема этой статьи — именно систематизация методики сравнения. Выстраивать в ряд зависящие от окружения цифры, которые выглядят правдоподобно, — это, по сути, гадание, поэтому здесь не будет рейтинга по реальным измерениям. Вместо этого мы сосредоточимся на том, как спроектировать сравнение, чтобы оно действительно имело ценность.

Сначала — вывод

В сравнении скорости C# / C++ / Java / Go по-настоящему важны эти семь пунктов.

  1. Сначала определить, какую именно скорость вы хотите сравнить Способ измерения меняется в зависимости от того, идёт ли речь о времени запуска, throughput в установившемся режиме, задержке p95 или эффективности использования памяти.

  2. Не делать выводы по одному-единственному бенчмарку На вычислениях CPU, выделении памяти, параллельной обработке и времени запуска сильными выглядят разные языки и рантаймы.

  3. Разделять cold и warm для C# и Java Если смешать сравнение, включающее первый запуск, со сравнением установившегося состояния после warm-up, вся картина искажается.

  4. Измерять с одним и тем же алгоритмом, одними и теми же входными данными и одной и той же проверкой корректности Классическая ловушка бенчмарков: дело не в том, что реализация быстрее, а в том, что она решала другую задачу.

  5. Разделять микробенчмарки внутри языка и сквозные (end-to-end) бенчмарки между языками Специализированный harness каждого языка удобен, но для сравнения между языками правильнее использовать общий внешний runner.

  6. Смотреть не только на среднее, но и на медиану и распределение Достаточно всего одного попадания GC или фоновой обработки, чтобы среднее значение исказилось.

  7. Сохранять не только цифры, но и условия Результат бенчмарка — это одновременно и запись о скорости, и запись об условиях эксперимента. Результаты без описанных условий потом доставляют немало боли.

Что нужно решить в первую очередь

Если ограничиться одним словом «быстро», почти наверняка что-то пойдёт не так. Сначала нужно решить, что именно вы называете «быстрым».

Например, даже для одной и той же программы то, что вы хотите увидеть, может сильно различаться.

1. Хотите ли вы увидеть время запуска

Для CLI-инструментов, короткоживущих пакетных задач и вспомогательных утилит, которые запускаются один раз и сразу завершаются, важны cold start и process startup. По этой оси результат сильно зависит от того, включены ли издержки инициализации JIT и загрузки классов.

2. Хотите ли вы увидеть throughput при длительной работе

Для серверов, резидентных процессов, воркеров и долго работающих преобразований важен throughput в установившемся режиме (steady-state). В этом случае то, что первый запуск медленный сам по себе, не суть дела — главный вопрос в том, насколько стабильно и высоко показатель поднимается после warm-up.

3. Хотите ли вы увидеть tail latency

Для API, UI и близких к реальному времени процессов p95 / p99 порой важнее среднего значения. Даже если в среднем всё быстро, редкие крупные задержки болезненны с точки зрения пользовательского опыта и SLA.

4. Хотите ли вы учитывать эффективность использования памяти

Если смотреть только на время CPU и не учитывать пиковый RSS, объём выделений, число сборок мусора и паузы GC, легко неверно оценить реальную нагрузку в эксплуатации. «Быстро, но съедает много памяти» и «немного медленнее, но стабильно легковесно» — оценка может перевернуться в зависимости от сценария использования.

Иными словами, вопрос, который нужно решить в первую очередь, звучит так:

В этом сравнении важно узнать не то, какой язык быстрее, а какой workload, при каких условиях и по какому показателю обрабатывается быстрее.

Если собирать цифры, оставляя это неопределённым, в конце концов ничего не сойдётся воедино.

Почему сравнивать языки сложно

Смешивание JIT и AOT превращает всё в другой эксперимент

C# и Java обычно подвержены влиянию JIT. C++ и Go, напротив, как правило, скомпилированы заранее.

То есть если измерять первый запуск, вы измеряете не только скорость самой программы, но заодно и запуск рантайма, загрузку классов и подготовку JIT. И наоборот, если смотреть только на состояние после достаточного warm-up, сравнение превращается в вопрос о том, насколько далеко заходит оптимизация в установившемся режиме.

Оба варианта осмысленны. Но это не одно и то же.

Разница в реализации нередко больше разницы между языками

Даже для одной и той же «сортировки»:

  • одна сторона использует стандартную библиотеку,
  • другая — самописную реализацию,
  • одна делает лишние копии,
  • другая каждый раз заново генерирует входные данные.

Одного этого достаточно, чтобы результат сильно изменился.

Более того, когда речь заходит о таких операциях, как JSON, сжатие, криптография или регулярные выражения, разница в реализации библиотек влияет куда сильнее, чем сам язык. Поэтому, если явно не указать, что именно измеряется, то, что задумывалось как «сравнение языков», на деле превращается в «сравнение библиотек».

У C++ есть ловушка, когда оптимизация убирает обработку целиком

Особенно в микробенчмарках, если компилятор решает, что «результат этого вычисления никто не использует», он может просто выбросить саму обработку. И тогда получается не «быстро», а маленькая страшилка: на самом деле ничего не выполняется.

В C++ эта проблема проявляется особенно откровенно, поэтому очень важно использовать результат, выводить checksum или задействовать средства подавления оптимизаций из benchmark-фреймворка.

Наличие GC — не «плюс» и не «минус», а особенность

В C#, Java и Go есть сборщик мусора (GC). Свести это просто к «раз есть GC, значит, медленно» — слишком грубое упрощение.

На практике сильнее влияют:

  • как обрабатывается большое количество короткоживущих объектов;
  • настройка размера кучи;
  • частота и паузы сборки мусора;
  • расположение объектов в памяти;
  • привычки библиотек к выделению памяти.

В противоположность этому, C++ позволяет точно управлять памятью вручную и через RAII, но именно поэтому разница в проектировании и реализации проявляется сильнее. Иными словами, разница в способе управления памятью сама по себе не является приговором «хорошо» или «плохо».

Чего нельзя делать при сравнении

1. Смешивать Debug и Release

Это вообще не обсуждается. Объекты сравнения обязательно должны быть выровнены на оптимизированную сборку, эквивалентную продакшену.

2. Решать разные задачи

Разный формат входных данных, разный вывод, обработка ошибок присутствует только у одной стороны, разная политика повторного использования памяти. Если оставить это без внимания, вы измерите не скорость, а разницу в требованиях.

3. Делать вывод по одному запуску

Один-единственный запуск — это по большей части шум.

  • JIT
  • кэш страниц
  • буст CPU
  • нагрев
  • фоновые задачи
  • GC
  • первое чтение файла

Всё это смешивается в одном запуске.

4. Смешивать warm-up

Когда вы измеряете C# и Java, если оставить неопределённым, включать ли первый запуск или смотреть только на состояние после warm-up, обсуждение разваливается. Cold и warm нужно рассматривать как разные вещи.

5. Не проверять корректность

Прежде чем быть «быстрым», бенчмарк должен «возвращать одинаковый результат». Обязательно проверяйте, что все сравниваемые реализации дают одинаковый checksum или одинаковый вывод при одинаковом входе.

6. Строить всю картину мира по одному микробенчмарку

Победа только в tight loop не гарантирует победы во всём реальном сервисе. И наоборот, проигрыш по времени запуска не мешает быть вполне сильным при долгой работе.

Базовый подход к сравнению C# / C++ / Java / Go

Этот момент довольно важен. Рекомендуемый подход — двухуровневая структура.

1. Для измерений внутри языка используйте подходящий для него harness

У каждого языка есть инструменты для бенчмарков, которые берут на себя специфику этого языка.

  • C#: BenchmarkDotNet
  • Java: JMH
  • Go: go test -bench и benchstat
  • C++: Google Benchmark

Они в той или иной мере заботятся об особенностях рантайма, статистической обработке и типичных ловушках измерения. Они весьма эффективны для сравнения внутри одного языка и для углублённого разбора реализации.

2. Для сравнения между языками разместите общий runner снаружи

С другой стороны, сопоставлять результаты BenchmarkDotNet для C# и результаты JMH для Java напрямую немного рискованно. Сами harness-ы следуют разным соглашениям.

Поэтому для сравнения между языками рекомендуется оформить каждую реализацию в виде исполняемого файла, вызываемого по одному и тому же CLI-контракту, и запускать их извне в одинаковых условиях.

Например, для каждого языка готовится исполняемый файл такого вида.

bench --scenario sort_int32 --dataset data/sort_10m.bin --mode warm
bench --scenario group_words --dataset data/words_100mb.txt --mode cold
bench --scenario parallel_hash --dataset data/blob_1gb.bin --threads 8

А со стороны общего runner-а реализуется такой поток:

  • рандомизировать порядок выполнения;
  • разделять cold / warm;
  • передавать один и тот же набор данных;
  • проверять checksum;
  • собирать wall-clock и память;
  • сохранять raw data в CSV / JSON.

Так становится проще разделять лучшие практики внутри каждого языка и объективность сравнения между языками.

Конкретный пример: какие сценарии бенчмарков подготовить

Когда просят «сравнить C# / C++ / Java / Go», если ограничиваться одним тестом, рекомендуется взять простой CPU-ориентированный сценарий, который трудно истолковать неверно; если тестов несколько — подготовить 3–4 workload-а с разным характером.

Рекомендуемый набор

1. sort_int32_10m

Цель: оценить CPU + пропускную способность памяти + использование временной области

  • Вход: 10 миллионов значений int32, сгенерированных с фиксированным seed
  • Обработка: отсортировать массив и вернуть checksum
  • На что обратить внимание: каждый раз возвращаться к одному и тому же неотсортированному входу

Это относительно понятный сценарий. Однако он включает и разницу в реализациях стандартной сортировки, поэтому это скорее сравнение с учётом стандартной библиотеки, чем сравнение самого языка.

2. hash_group_count

Цель: оценить хеш-таблицы, обработку строк, выделение памяти и тенденции GC

  • Вход: фиксированные текстовые данные
  • Обработка: подсчёт частоты встречаемости каждого слова
  • Вывод: топ-N записей и checksum

Это близко к реальной практике, но и разница в реализации строковых библиотек и map заметно влияет на результат. Зато сравнение получается ближе к реальности.

3. parallel_sha256

Цель: оценить параллельную обработку, планировщик, пул воркеров и особенности синхронизации

  • Вход: последовательность бинарных чанков фиксированного размера
  • Обработка: хеширование в N потоках с возвратом итогового checksum
  • Условия: число потоков ступенчато меняется — 1 / 2 / 4 / 8 и т. д.

По сравнению с простым tight loop здесь гораздо лучше видно, как растёт производительность при параллельном выполнении.

4. startup_noop или startup_parse_small

Цель: оценить время запуска

  • noop: запуск и немедленное завершение
  • parse_small: однократная обработка небольшого входа с последующим завершением

Здесь хорошо видны JIT и издержки инициализации C# / Java, и картина заметно отличается от C++ / Go. Иначе говоря, даже если здесь появится разница, она не определяет победителя в задачах, работающих долго.

Как быть с бенчмарками JSON и HTTP

JSON и HTTP близки к реальной практике, так что в них, конечно, есть смысл. Но в этом случае получается скорее сравнение с учётом библиотек, фреймворков и экосистемы, чем сравнение самих языков.

Само по себе это не плохо. На практике такое сравнение нередко даже важнее. Но в статье или отчёте меньше поводов для недопонимания, если прямо указать:

Это не сравнение языков, а сравнение типовых реализаций вместе с основными библиотеками.

Условия, которые нужно выровнять для каждого языка

C++

  • Выровнять на оптимизированную сборку
  • Зафиксировать компилятор
  • Зафиксировать реализацию стандартной библиотеки
  • Явно указать условия вроде -O3 / /O2, LTO, PGO
  • Следить, чтобы результат не был убран оптимизацией
  • Проверить, не выглядит ли код быстрым из-за неопределённого поведения

Поскольку C++ даёт много свободы, разница в условиях проявляется особенно сильно. Поэтому крайне важно, каким компилятором, с какими флагами и с какой STL проводилось измерение.

C#

  • Выровнять на Release-сборку
  • Зафиксировать версию .NET
  • Зафиксировать условия вроде Server GC / Workstation GC
  • Явно указать, используются ли Tiered Compilation, ReadyToRun, Native AOT
  • Разделять cold и warm

Для C# разница в настройках .NET меняет картину. В частности, C# с JIT и C# с Native AOT — это разные оси, даже если оба называются «C#». Если их смешать, объектом сравнения окажется не язык, а форма распространения.

Java

  • Зафиксировать вендора и версию JDK
  • Явно указать GC
  • Зафиксировать warm-up / measurement / fork
  • Записать размер кучи и опции JVM
  • Разделять cold start и steady-state

Java легко получает выгоду от JIT, но взамен то, как выглядит первый запуск, сильно варьируется. Поэтому обязательно разделять сравнение короткоживущих процессов и сравнение при долгой работе.

Go

  • Зафиксировать версию Go
  • Зафиксировать GOMAXPROCS
  • Явно указать CGO_ENABLED
  • Если меняете GOGC, обязательно фиксировать это
  • По возможности сохранять вывод в формате benchmark

С Go сравнительно легко работать, но в параллельных бенчмарках сильно сказывается влияние GOMAXPROCS. Кроме того, использование cgo или его отсутствие полностью меняет картину, поэтому это обязательно нужно фиксировать в условиях.

Как выровнять окружение выполнения

На каком бы языке ни шла речь, сравнение без выровненного окружения на деле почти всегда оказывается сравнением самих окружений.

Что нужно выровнять

  • Один и тот же CPU / память / хранилище
  • Одна и та же версия ОС
  • Одни и те же условия электропитания
  • Условия, максимально близкие к одной и той же комнатной температуре
  • Одни и те же входные данные
  • Один и тот же приоритет процесса
  • Одни и те же условия по числу ядер
  • Одни и те же условия контейнера или bare-metal

Что влияет особенно сильно

Настройки электропитания и частота CPU

На ноутбуке одно только различие между питанием от сети и от батареи создаёт совершенно разные условия. Если не выровнять CPU governor или режим питания, результаты сравнения сильно плавают.

Подробнее о том, как выровнять условия электропитания, уведомления, фоновый шум, нагрев и порядок выполнения в Windows, рассказано в отдельной статье Как сравнивать скорость выполнения разных версий программы в Windows. Если измерения проводятся в Windows, этот момент имеет большое значение.

Нагрев

Если быстрыми оказываются только первые несколько запусков, а дальше показатели падают, стоит заподозрить нагрев и троттлинг. Вместо того чтобы прогнать все запуски A, а потом все запуски B, чередование в духе A / B / A / B снижает перекос.

Фоновые процессы

Обновления, индексация, синхронизация, антивирусное сканирование, браузер, чат-приложения. Это неприметные вещи, но они регулярно вмешиваются в результат.

Что следует измерять

В сравнении языков рекомендуется как минимум рассматривать отдельно эти четыре показателя.

1. Wall-clock time

Реальное время, которое ждёт пользователь. Это первый показатель, на который стоит смотреть.

2. CPU time

Показывает, «сколько реально было использовано CPU». Если ускорился только wall-clock, а CPU time не изменилось, возможно, дело во времени ожидания или влиянии I/O.

3. Память / выделения

  • Пиковый RSS
  • Общий объём выделений
  • Число вызовов alloc
  • Число сборок мусора
  • Паузы GC

Если посмотреть на эти показатели, становится видна цена, скрытая за скоростью.

4. Распределение

  • Медиана
  • p95 / p99
  • min / max
  • Стандартное отклонение и разброс

Если судить только по среднему, не видно истинной природы редких выбросов.

Рекомендуемая процедура выполнения

Порядок, удобный для реальной практики, выглядит примерно так.

1. Определить workload

Прежде всего чётко сформулировать, что именно вы хотите сравнить.

  • время запуска
  • throughput в установившемся режиме
  • tail latency
  • эффективность использования памяти
  • масштабирование при параллелизме

2. Зафиксировать общий набор данных

Входные данные выравниваются через фиксированный seed или фиксированный файл. Если генерация данных тоже входит в измерение, она должна проходить в одинаковых условиях для каждого языка.

3. Сначала пройти проверку корректности

Убедиться, что на небольших и на больших данных все реализации возвращают одинаковый результат. Удобно, если реализации выводят checksum или хеш.

4. Зафиксировать условия сборки

Для каждого языка собрать Release / оптимизированный исполняемый файл и зафиксировать версии и флаги.

5. Разделить cold и warm

Это особенно важно для C# и Java.

  • cold: включает момент сразу после запуска процесса
  • warm: стабильное состояние после нескольких запусков

Эти два варианта лучше не смешивать в одной таблице.

6. Чередовать или рандомизировать порядок выполнения

Пример:

cpp -> csharp -> java -> go
go -> java -> cpp -> csharp
csharp -> go -> java -> cpp
...

Так снижается перекос от нагрева и шума.

7. Обеспечить достаточное число повторов

Для лёгких микробенчмарков стоит брать заметно больше повторов, для end-to-end — не менее 10. Если разница мала, а число повторов невелико, интерпретация становится довольно ненадёжной.

8. Сохранять raw data

Оставлять не только агрегированные результаты, но и исходные данные каждого прогона. При взгляде позднее по ним можно прочитать выбросы и особенности warm-up.

9. При появлении разницы снять профиль

Только когда разница действительно обнаружилась, стоит копать причину.

  • CPU-профиль
  • профиль выделений памяти
  • логи GC
  • flame graph
  • трассировка на уровне ОС

Дойдя до этого этапа, вы сможете говорить не просто «быстро / медленно», а почему так происходит.

Как читать результаты

Даже после того как цифры получены, неверное прочтение результатов по-прежнему опасно.

C# / Java медленные только на первом запуске

Стоит заподозрить влияние JIT, загрузки классов и инициализации. В этом случае:

  • если важно время запуска — это значимая разница;
  • если тема — долгая работа — это разница, которую стоит вынести в отдельную таблицу.

C++ силён в tight loop

Возможно, здесь сказываются низкоуровневые оптимизации, расположение объектов и минимальные накладные расходы рантайма. Однако делать вывод «значит, и в реальном сервисе он будет самым быстрым», опираясь только на это, — слишком большой скачок.

Go выглядит выгодно по времени запуска и удобству распространения

Здесь может сказываться единый бинарник, относительно лёгкий старт и удобная модель параллелизма. Однако это не означает преимущества во всех CPU-ориентированных workload-ах.

C# / Java заметно догоняют или даже обгоняют в steady-state

Возможно, здесь работают оптимизации JIT. Это тоже не редкость. Поэтому важно не смешивать сравнение с учётом запуска и сравнение в установившемся режиме.

Большая разница в операциях с интенсивным выделением памяти

В этом случае обычно сильнее влияет не название языка, а:

  • расположение данных в памяти,
  • обработка строк и map,
  • поведение GC,
  • лишние копии.

Шаблон записи результатов

Если сохранять для результатов бенчмарка как минимум следующие поля, это здорово поможет впоследствии.

timestamp,language,scenario,run_kind,cold_or_warm,elapsed_ms,cpu_ms,max_rss_mb,alloc_bytes,gc_count,checksum
compiler_or_runtime,compiler_version,flags,os,cpu,threads,input_id,notes

Например, run_kind можно разделить так:

  • micro
  • macro
  • startup
  • parallel

Для cold_or_warm обязательно нужно явно указывать, о каком из вариантов идёт речь.

  • cold
  • warm

В бенчмарках порой важнее не сам факт измерения, а возможность впоследствии интерпретировать результат.

Заключение

По-настоящему важно в сравнении скорости C# / C++ / Java / Go — превратить грубый вопрос «какой язык самый быстрый» в форму эксперимента: «какой workload, при каких условиях и по какому показателю мы сравниваем».

Особенно трудно ошибиться, если придерживаться следующего.

  • Разделять время запуска и установившееся состояние
  • Измерять с одним и тем же алгоритмом, одними и теми же входными данными и одной и той же проверкой корректности
  • Не делать выводы по одному-единственному бенчмарку
  • Разделять бенчмарки внутри языка и между языками
  • Смотреть на медиану и распределение, а не только на среднее
  • Сохранять условия и raw data

И самое важное в конце: не пытаться слишком настойчиво определять победителя по одному лишь названию языка. Реальная производительность складывается из комбинации языка, рантайма, библиотек, условий сборки, данных, ОС и оборудования.

«C++ быстрый», «Java сильна», «Go лёгкий», «C# тоже вполне быстрый» — в каком-то смысле все эти утверждения верны. Но если из них выпадает, при каких условиях это сказано, разговор обычно превращается в драку в тумане.

Выровнять условия, использовать несколько workload-ов, разделять cold / warm и смотреть вплоть до распределения. Неброско, но в итоге именно это оказывается самым надёжным подходом.

Справочные материалы

  • BenchmarkDotNet Getting Started https://benchmarkdotnet.org/articles/guides/getting-started.html

  • OpenJDK JMH Project https://openjdk.org/projects/code-tools/jmh/

  • JMH GitHub Repository / README https://github.com/openjdk/jmh

  • Go testing package https://pkg.go.dev/testing

  • Go benchstat https://pkg.go.dev/golang.org/x/perf/cmd/benchstat

  • Google Benchmark User Guide https://google.github.io/benchmark/user_guide.html

  • Как сравнивать скорость выполнения разных версий программы в Windows https://comcomponent.com/ru/blog/2026/03/16/002-windows-benchmark-comparing-program-versions/

Похожие темы

Эти страницы легче понять, если посмотреть их вместе с этой статьёй.

Где обсудить эту тему

Проектирование сравнения производительности, выравнивание условий измерения, интерпретация результатов и углублённый разбор причин хорошо сочетаются со следующими услугами.

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Технические консультации и ревью дизайна

Тема хорошо подходит для технической консультации и ревью архитектуры — она включает проектирование сравнения производительности, выравнивание условий измерения, а также работу с warm-up и чтением статистики.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Какой язык быстрее всех — C#, C++, Java или Go?
Одним числом это не определить. Реальная производительность складывается из комбинации языка, рантайма, библиотек, условий сборки, данных, ОС и оборудования. Важно превратить вопрос «какой язык самый быстрый» в форму эксперимента: «какой workload, при каких условиях и по какому показателю мы сравниваем». На старте программы хорошо заметны JIT и издержки инициализации в C#/Java, а вот в steady-state оптимизации JIT часто позволяют почти догнать конкурентов или даже обойти их — и это не редкость.
Почему в бенчмарках C# и Java нужно отдельно учитывать warm-up?
C# и Java обычно подвержены влиянию JIT, поэтому при измерении первого запуска вы измеряете не только скорость самой программы, но и запуск рантайма, загрузку классов и подготовку JIT. C++ и Go, напротив, обычно скомпилированы заранее. И cold, и warm имеют смысл, но это не одно и то же, поэтому cold, включающий момент сразу после старта процесса, и warm — стабильное состояние после нескольких запусков — стоит рассматривать как разные вещи и не смешивать в одной таблице.
Как правильно спроектировать бенчмарк, охватывающий несколько языков?
Рекомендуется двухуровневая структура. Для измерений внутри языка используйте подходящий для него harness: BenchmarkDotNet (C#), JMH (Java), go test -bench и benchstat (Go), Google Benchmark (C++). Для сравнения между языками напрямую сопоставлять результаты разных harness-ов опасно, поэтому правильнее оформить каждую реализацию в виде исполняемого файла с одинаковым CLI-контрактом и запускать их из общего внешнего runner-а, который рандомизирует порядок выполнения, разделяет cold/warm, использует один и тот же набор данных, проверяет checksum и сохраняет raw data.
На что обратить внимание в микробенчмарках C++?
Нужно остерегаться ловушки, когда оптимизация полностью убирает вычисления. Если компилятор решает, что «результат этого вычисления никто не использует», он может выбросить саму обработку — и тогда результат не «быстрый», а просто «ничего не делает». Поэтому важно использовать результат, выводить checksum и задействовать встроенные средства benchmark-фреймворка для подавления таких оптимизаций. Кроме того, в C++ разница в условиях сказывается особенно сильно, поэтому важно чётко фиксировать, каким компилятором, с какими флагами (-O3/O2, LTO, PGO и т. д.) и с какой STL проводились измерения.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог