Вышла часть черновика книги Шерри Игнатенко и Дмитрия Иванчихина «Efficient C++ Programming for Modern 64-bit CPUs». В ней — сводка реальных затрат в тактах CPU для ключевых операций, от деления до контекстных переключений.
Деление остаётся самым дорогим арифметическим действием. IDIV R64 на Skylake-X выдаёт 37–96 тактов, на Zen 4 — 9–19. Умножение (IMUL) для 32 и 64 бит почти одинаково — 3 такта, кроме некоторых E-cores. Все остальные целочисленные операции (кроме умножения и деления) дешёвые — обычно 1–2 такта, вне зависимости от разрядности.
RTTI и dynamic_cast<> могут быть до пяти раз дороже обычного виртуального вызова. При этом RTTI не добавляет данных в объекты полиморфных классов — использует уже существующий vfptr.
C++ исключения выгодны только при редких ошибках: успешный путь почти бесплатен (на 2 такта дороже обычного возврата), но при срабатывании исключение стоит 2700–5000 тактов. Если у вас хотя бы одно исключение на 100 вызовов — выгоднее возвращать код ошибки. Авторы подчёркивают, что утверждение из старой книги Агнера Фога о том, что все функции платят за исключения, безнадёжно устарело — современные компиляторы используют zero-cost exceptions с табличным подходом.
Атомики на современных NUMA-системах стоят примерно 15 тактов для CAS на одном сокете, но в многосокетных конфигурациях — до 300–600. Они всё ещё мешают параллелизму на уровне инструкций, снижая пропускную способность до 30 раз по сравнению с обычными записями. RISC-V использует механизм LL/SC, а не CAS.
Прямые вызовы функций обходятся в 15–30 тактов, косвенные — 20–50, виртуальные — 30–60. Но главная потеря — упущенная оптимизация: инлайнинг позволяет компилятору переписывать код целиком. В примере авторов square() и cube() без инлайнинга дают три умножения, а с инлайнингом — два, потому что компилятор сворачивает x*x + x*x*x в (x*x)*(1+x).
thread_local в GCC и Clang под x64 добавляет одну косвенность через FS-регистр, а в MSVC — до трёх.
И наконец, переключение контекста: 2000 тактов — только прямое переключение, а косвенное из-за инвалидации кэша может достигать 3 миллионов тактов, а в худшем сценарии — до 30 миллионов. Именно поэтому CRITICAL_SECTION в Windows по умолчанию крутится 4000 раз в спинлоке, просто чтобы не делать контекстный свитч.