Цена операций в процессорных тактах

Авторы Игнатченко и Иванчикин опубликовали вторую часть главы 4 из готовящейся книги про эффективный C++. В ней они собрали известные цены основных операций в тактах процессора, которые часто нужны разработчикам, чтобы понять, где искать узкие места в коде.
Деление остаётся дорогим: на современных 64-битных CPU оно занимает около 15 тактов, тогда как умножение - всего 3-5. Исключения обходятся в 2700-5000 тактов при возникновении, но если ошибки редкие (раз на 10000 вызовов и реже), они дешевле, чем проверка кодов ошибок. Контекстные переключения потоков стоят 10000-100000 тактов в прямых затратах, но косвенные потери из-за инвалидации кэша достигают миллионов тактов.
Ключевые факты
- Деление на 64-битных CPU занимает 15 тактов, в многосокетных системах может доходить до 600 тактов
- Умножение и деление остаются дорогими операциями, все остальные целочисленные операции - 1-2 такта
- Исключения при редких ошибках (раз на 10000+ вызовов) эффективнее кодов ошибок, но если ошибка происходит, стоит 2700-5000 тактов
- Вызовы функций обходятся в 15-30 тактов в прямых затратах, виртуальные вызовы дороже - 30-60 тактов
- Контекстные переключения стоят 10000-100000 тактов прямых затрат, косвенные потери из-за кэша достигают 3 миллионов тактов
Ред. Деление в 15 тактов против умножения в 5 знают наизусть, а вот про инвалидацию кэша в миллионы тактов вспоминают уже на проде. Самая дорогая операция в списке (контекстный свитч) почти не видна в профайлере.
Почему это важно
Разработчики часто полагаются на микробенчмарки, которые дают искажённую картину реальной производительности. Точные числа операций в тактах помогают предсказать поведение кода без запуска тестов. Эта информация особенно ценна при оптимизации критических участков, когда нужно выбрать между несколькими подходами: например, стоит ли избегать деления, если оно встречается редко, или лучше переписать весь алгоритм.
Ред. Любопытная инверсия: чтобы не доверять микробенчмаркам, авторы предлагают доверять таблице чисел. Ориентир полезный, но это всё ещё замена одного грубого инструмента другим, просто без запуска.
Кому это важно
Разработчикам на C++ и системных программистам, которые оптимизируют производительность под конкретные процессоры. Авторы библиотек, работающие с критичными по скорости модулями. Инженеры, которые проектируют многопоточные системы и должны понимать цену контекстных переключений.
Ред. Тем, кто оптимизирует горячие циклы под конкретный CPU. Остальные 95 процентов кода, где узкое место это поход в базу по сети, эту таблицу могут спокойно не открывать.
Как это применить
При профилировании кода используйте эту таблицу как первый ориентир: если в критичной функции есть деления, рассмотрите их переписывание. Для исключений проверьте частоту их возникновения - если ошибки чаще, чем раз на 10000 вызовов, лучше вернуть код ошибки. Для многопоточного кода минимизируйте контекстные переключения, потому что косвенные потери от инвалидации кэша могут быть в 10-100 раз больше прямых затрат.
Ред. Совет про деление и исключения разумный, но финал честнее самой главы: косвенные потери от свитчей в 10-100 раз больше прямых, то есть единственное число из таблицы, которое реально решает, это то, которое в таблицу толком не помещается.
Можно ли доверять
Это черновик будущей книги, авторы сами указывают, что все числа точны только с точностью до порядка величины. Они опираются на авторитетные источники (Agner Fog, университетские исследования), но признают, что часть данных может быть устаревшей и нуждается в собственном тестировании. Разработчики приглашают читателей указывать на ошибки.
Ред. Авторы заранее снимают с себя ответственность («точность до порядка величины», «часть данных устарела», «проверяйте сами»). Честно, но это означает, что таблицу цен надо перепроверять на своём железе, то есть ровно тем самым тестированием, которое она обещала заменить.
Риски и подводные камни
Числа варьируются в зависимости от архитектуры и даже поколения CPU - то, что верно для x64 Skylake, может не работать для ARM. Косвенные потери от контекстных переключений зависят от паттернов доступа в памяти, и в худшем случае (полностью случайный доступ) могут достигать 30 миллионов тактов. Инвалидация кэша часто скрыта и не видна в простых бенчмарках.
Ред. x64 Skylake и ARM живут по разным прайс-листам, а worst-case по свитчам разъезжается с 3 до 30 миллионов тактов в зависимости от паттерна доступа. Самые дорогие строки прайса при этом невидимы в простом бенчмарке, так что подводный камень тут это вся подводная часть.