Я узнаю о разворачивании цикла, чтобы избежать киосков, вызванных зависимостями. Я нашел много примеров в Интернете и в литературе, но не нашел объяснений, как алгоритм, используемый для получения оптимизированного кода, работает (в случае, если есть один такой алгоритм, конечно). В частности, я не знаю, как определить, сколько раз цикл должен быть развернут. Может ли он быть рассчитан заранее?
Сколько раз цикл должен быть отключен?
Ответ 1
Правило большого пальца состоит в том, что вы расслабляетесь так, чтобы:
- Операции
- выполняются на "естественных" границах 4,8,16,32.. байты
- вы не вводите чрезмерное давление в регистре (т.е. вы не запускаете жонглирование регистров в память)
- вы не начинаете повторять одну и ту же последовательность команд снова и снова
В основном вы разворачиваетесь до тех пор, пока можете добавить больше ресурсов для работы, и остановитесь, когда вы больше не сможете измерить прирост производительности.
Ответ 2
Вы пишете компилятор? В противном случае вы действительно не должны делать цикл, расслабляющий себя. Скорее всего, вы должны доверять компилятору, чтобы сделать правильный цикл для вас, где он находит его применимым.
Ответ 3
Иногда имеет смысл не развернуть цикл (для процессоров Core2 и выше), потому что у них есть "детектор Loop stream" (они называют его LSD). Просто просмотрите его в Руководстве по оптимизации Intel.
Если код действительно вписывается в (очень маленькую) очередь, то процессору не нужно извлекать/декодировать инструкции из L1-Instruction-Cache, которые могут дать некоторую производительность.
Ответ 4
Я хотел бы добавить свой ответ, потому что, хотя ответ Torbjörn Gyllebring хороший, он не является полным IMO
Существуют различные улучшения, связанные с разворачиванием:
Алгоритмическое улучшение. ваш набор инструкций позволяет обрабатывать четыре, а не один байт. Torbjörn ответил, что отлично.
Сократить накладные расходы на цикле, когда тело цикла невелико, накладные расходы цикла (как правило, increment + compare + jump) потребляют значительное количество времени.
total cost = N * (loop body + loop overhead)
с разворачиванием один раз, вы получите
total cost = N/2 * (2 * loop body + loop overhead)
= N * loop body + N / 2 * loop overhead
если накладные расходы цикла малы по сравнению с телом цикла, разворачивание не даст вам никакой выгоды за счет увеличения размера кода. Пример. Когда тело цикла имеет накладные расходы на 10x, разворот дает в лучшем случае 5% -ное улучшение.
Лучшее сопряжение - на архитектурах с несколькими конвейерами (или квази-спаривание, например, переименование регистров и генерация микрокода), разворот может дать гораздо лучшие возможности сопряжения. Опять же, они будут заметны только тогда, когда тело цикла мало, но формулу нельзя дать так просто, как описано выше.
Unrolling не безвреден - имейте в виду, что разворачивание даже в "хороших" случаях почти удваивает размер кода цикла. Это может очистить другой код или данные из вашего кеша. На современных архитектурах настольных компьютеров проблемы с размерами кода достаточно разрывы, поэтому эмпирическое правило состоит в том, чтобы оптимизировать размер кода во всем мире и оптимизировать скорость только локальных горячих точек.