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

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

Авторы Игнатченко и Иванчикин опубликовали вторую часть главы 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 миллионов тактов

Почему это важно

Разработчики часто полагаются на микробенчмарки, которые дают искажённую картину реальной производительности. Точные числа операций в тактах помогают предсказать поведение кода без запуска тестов. Эта информация особенно ценна при оптимизации критических участков, когда нужно выбрать между несколькими подходами: например, стоит ли избегать деления, если оно встречается редко, или лучше переписать весь алгоритм.

Кому это важно

Разработчикам на C++ и системных программистам, которые оптимизируют производительность под конкретные процессоры. Авторы библиотек, работающие с критичными по скорости модулями. Инженеры, которые проектируют многопоточные системы и должны понимать цену контекстных переключений.

Как это применить

При профилировании кода используйте эту таблицу как первый ориентир: если в критичной функции есть деления, рассмотрите их переписывание. Для исключений проверьте частоту их возникновения - если ошибки чаще, чем раз на 10000 вызовов, лучше вернуть код ошибки. Для многопоточного кода минимизируйте контекстные переключения, потому что косвенные потери от инвалидации кэша могут быть в 10-100 раз больше прямых затрат.

Можно ли доверять

Это черновик будущей книги, авторы сами указывают, что все числа точны только с точностью до порядка величины. Они опираются на авторитетные источники (Agner Fog, университетские исследования), но признают, что часть данных может быть устаревшей и нуждается в собственном тестировании. Разработчики приглашают читателей указывать на ошибки.

Риски и подводные камни

Числа варьируются в зависимости от архитектуры и даже поколения CPU - то, что верно для x64 Skylake, может не работать для ARM. Косвенные потери от контекстных переключений зависят от паттернов доступа в памяти, и в худшем случае (полностью случайный доступ) могут достигать 30 миллионов тактов. Инвалидация кэша часто скрыта и не видна в простых бенчмарках.