Комментарии 16
Не разработчик, мало что понял. Но то что понял - вызывает уважение к изложению и формулировкам в статье. Спасибо.
Місль о том, что эффективная оптимизация происходит на этапе компиляции и следовательно имеет смысл сливать модули в один файл (перед компиляцией)- новая для меня
как минимум ты выигрываешь от того, что компилятор по 100 раз не перекомпилирует одни и те же хедера, которые многократно включаются через include
есть еще интересный случай: на старых монструозных проектах прямая сборка бинарей из исходников (без промежуточных библиотек) может оказаться в несколько раз быстрее (напр. 40 секунд вместо 15 минут).
а именно. вместо старого "логичного метода":
компилируем и собираем в библиотеку общие файлы бинарей
компилируем уникальные файлы бинаря
линкуем их с библиотекой
сразу вызываем:
g++ mysrc1/*.cpp mylib/*.cpp -o mybin1
ЗЫ конечно не все *.cpp а только лишь нужные файлы. пробуй.
// Сгенерированный код сбивает с толку, потому что
// компилятор превратил деление в умножение.
return a / 3;
Что? У меня с глазами что-то?
Смысл в том, что на уровне исходного кода сделали инлайнинг и выкинули проверку, оставив "a / 3", а дальше на этапе генерации бинарника компилятор заменяет деление на константу умножением со сдвигом (видно по ссылке на godbolt).
нечто, кажущееся невинным, например, структура программы, способно вызывать колебания результатов бенчмарков в пределах 40%
О да, не так давно обнаружилось, что на P-ядрах мобильных Raptor Lake вставка лишней (!) NOP-инструкции в выровненный на начало кеш-линии цикл может ускорить программу. То есть вот такое:
align 64
lp:
dec rcx
jnz lp
работало условно за 1.6 секунды без значительных колебаний, а вот такое:
align 64
lp:
dec rcx
nop
jnz lp
примерно за 0.95 — 1.25, каждый раз получалось случайное время в этом интервале (частота зафиксирована, Turbo Boost отключен). Может, я что-то сделал/понял не так — знающих прошу откликнуться.
Понятно, что пример синтетический, но всё же. Микроархитектура современных Intel — это вообще загадка, AMD в этом отношении более предсказуемы.
-O3 генерирует код, который гораздо быстрее кода с -O2
-O3
далеко не вершина эволюции, есть ещё более жёсткий -Ofast
. Но и он не даёт гарантий, что будет быстрее, надо бенчмаркать. Однако тут тоже может выясниться, что вариант -Ofast
на одном CPU работает быстрее, а на другом быстрее -O2
или даже -O0
(и такое бывает).
Встраивание в первую очередь полезно тем, что избавляет от команды вызова
Не только, ещё избавляет от кучи PUSH / POP перед входом и выходом из процедуры.
А статья и перевод замечательные, побольше бы таких.
Возможно тут есть ответ, но это не точно: https://easyperf.net/notes/ (я бы начал с постов про MicroFusion и MacroFusion).
Видел эти заметки, спасибо. Однако Micro/MacroFusion появились ещё во времена Core 2, а описанная странность проявлялась только на P-ядрах i7-13700H и не давала эффекта, например, на относительно свежем i5-10400 (Comet Lake). Звучит как особенность конкретной микроархитектуры, хотя тут я точно не специалист.
Надо было расчехлить VTune и детальнее исследовать метрики — возможно, из-за отсутствия времени забил.
Может фолдинг так странно работает.
Хорошая статья, но многое оставлено за скобками.. Оптимизация кода это не только предвычисление констант и инлайнинг функций. Теория говорит о 14 способах оптимизации, два последних не реализуются практически нигде, только для очень небольших кусков кода: полная оптимизация потока данных (изменение структур в т.ч.) и сквозная оптимизация потока управления, по сути изменение алгоритмов..
В целом авторы, как обычно, на высоте. Спасибо за статью, забывать начал премудрости компиляторов.
Развенчиваем популярные мифы и заблуждения о компиляторах