Shortcut & Serega
Serega Serega
Привет, Стрела, слышал, ты за рекорды на каждой новой карте гоняешься. Когда-нибудь задумывался, что принципы тайминга и оптимизации можно применить и к ускорению работы кода? Я тут поигрался с рекурсивным алгоритмом, и он в некоторых случаях быстрее итеративного получается на микросекунды. Интересно, поможет ли менталитет спидраннера выловить какую-нибудь микро-оптимизацию, которую даже опытный программист не заметит.
Shortcut Shortcut
Да, каждая секунда на счету везде. Запусти профилировщик, проверь на неправильные предсказания ветвлений, и сделай горячие пути максимально прямыми. Если найдёшь крошечное условие, которое можно убрать – это уже победа. Покажи мне микрооптимизацию, которую даже профи не заметили – вызов принят.
Serega Serega
Конечно, вот кусочек кода, который полностью исключает ветвление, используя трюк с битовой маской. Я преобразовал хвостовую рекурсивную функцию факториала в цикл и вместо условного ветвления использую умножение на маску. Код выглядит так: ```c++ int fact(int n){ int res=1; while(n>1){ int mask = -(n & 1); // все единицы, если нечетное, нули, если четное res *= (n & mask) | (~mask & 1); n--; } return res; } ``` Этот трюк с `mask` убирает ветвление `if (n%2)`, так что процессор не будет «зависать» из-за неправильного предсказания. Я профилировал его на свежем Intel Core и получил снижение на несколько сотен циклов на вызов по сравнению с обычным рекурсивным вариантом. Если запустишь его через `perf` или `gprof`, увидишь, как путь без ветвлений доминирует в горячем цикле. Сообщи, что покажет профилировщик — если смогу выжать ещё больше, отправлю следующую версию.
Shortcut Shortcut
Круто, этот трюк с маской обходит штраф за ветвление, но всё равно проверь цикл на возможность развёртывания. Может, разбей декремент на два шага, чтобы обрабатывать два числа за итерацию и не допускать постоянного перезаписи регистра умножением. Я запущу `perf record` для партии из тысячи вызовов и посмотрю, хватит ли ЦПУ, чтобы кормить арифметический блок без промахов кэша. Если количество циклов на итерацию снизится ещё, присылай следующий апгрейд. Давай продолжаем выбивать микросекунды.
Serega Serega
Понял, давай развернём две итерации. Так у тебя будет две операции умножения в работе одновременно, и задержка цикла уменьшится. Вот более компактная версия: ```c++ int fact_unroll(int n){ int res=1; while(n>1){ int a=n; int b=n-1; int maskA = -(a & 1); int maskB = -(b & 1); res *= (a & maskA) | (~maskA & 1); res *= (b & maskB) | (~maskB & 1); n -= 2; } return res; } ``` Я выровнял эти два умножения, чтобы арифметико-логический блок (ALU) был постоянно занят, и маска ветвления убирает условный переход. Если ты профилируешь это, то должен увидеть заметное снижение количества циклов на вызов. Напиши, какие результаты получишь, и тогда перейдём к векторизованной SIMD-версии.
Shortcut Shortcut
Зацени, как выложилось – здорово получилось. Запустил `perf` на тысяче вызовов, вижу падение циклов на вызов примерно на 30% по сравнению с одноцикличной версией, кэш ведет себя нормально. Следующий шаг: упаковываем эти два умножения в один `vmulps` на 256-битном регистре – SIMD даст еще прирост. Присылай черновик с SIMD, я прогоню его через профайлер. Поддержим эту эффективность на высоте.