Как оптимизирующие компиляторы решают, когда и сколько развернуть цикл? - PullRequest
10 голосов
/ 07 октября 2011

Когда компилятор выполняет оптимизацию развертывания цикла, как он определяется тем, какой фактор развернуть цикл или развернуть весь цикл? Поскольку это компромисс между космической производительностью, в среднем, насколько эффективен этот метод оптимизации для повышения эффективности программы? Кроме того, при каких условиях рекомендуется использовать эту технику (т.е. определенные операции или вычисления)?

Это не обязательно должно быть специфично для определенного компилятора. Это может быть любое объяснение, излагающее идею, лежащую в основе этой техники, и то, что наблюдалось на практике.

Ответы [ 4 ]

10 голосов
/ 07 октября 2011

Когда компилятор выполняет оптимизацию развертывания цикла, как он определяет, какой фактор развернуть цикл или погоду, чтобы развернуть весь цикл или нет.

потребление стека и локальность.количество командспособность делать / распространять оптимизации на основе развернутой и встроенной программы.является ли размер цикла фиксированным или ожидаемым будет в определенном диапазоне.профильные входы (если применимо).операции, которые могут быть удалены из тела цикла.и т. д.

Так как в среднем это компромисс между производительностью и пространством, насколько эффективен этот метод оптимизации для улучшения работы программы?

, это в значительной степени зависит от ввода (ваша программа).это может быть медленнее (не типично) или может быть в несколько раз быстрее.написание программы для оптимальной работы, которая также позволяет оптимизатору выполнять свою работу.

Кроме того, при каких условиях рекомендуется использовать эту технику (т.е. определенные операции или вычисления)

как правило, большое количество итераций на очень маленьких телах, особенно на тех, которые не имеют ответвлений и имеют хорошую локальность данных.

, если вы хотите знать, помогает ли опция вашему приложению, профилю.

Если вам нужно больше, вы должны уделить некоторое время, чтобы научиться писать оптимальные программы, поскольку предмет довольно сложный.

3 голосов
/ 07 октября 2011

Упрощенный анализ заключается в подсчете инструкций - цикл, состоящий из 2 инструкций, развернутых 10 раз, имеет 11 инструкций вместо 20, что дает ускорение 11/20.Но с современной процессорной архитектурой это намного сложнее;в зависимости от размеров кеша и характеристик конвейера команд процессоров.Возможно, что приведенный выше пример будет работать в 10 раз быстрее, чем в 2 раза.Также возможно, что развертывание 1000x вместо 10x будет работать медленнее.Не ориентируясь на конкретный процессор, компиляторы (или прагмы, которые вы пишете для них) просто догадываются.

1 голос
/ 07 октября 2011

Хорошо, во-первых, я не знаю, как компиляторы делают это автоматически. И я уверен, что есть хотя бы 10, если не 100 алгоритмов, из которых приходится выбирать компиляторам.
И в любом случае это, вероятно, зависит от компилятора.

Но я могу помочь вам с расчетом его эффективности.

Просто отметьте, что эта техника обычно не дает вам большого прироста производительности.
Но при повторных зацикленных расчетах и ​​может дать высокий процент производительности.
Это потому, что обычно функция внутри цикла занимает гораздо больше времени вычислений, чем проверка состояния цикла.

Итак, допустим, у нас есть простой цикл с константой, потому что вам было лень копировать-вставлять или просто думали, что это будет выглядеть лучше:

for (int i = 0; i < 5; i++)
{
    DoSomething();
}

Здесь у вас есть 5 int сравнений, 5 приращений и 5 DoSomethig () вызовов.
Так что если DoSomething () работает относительно быстро, то мы получаем 15 операций.
Теперь, если вы развернете это, вы уменьшите его до 5 операций:

DoSomething();
DoSomething();
DoSomething();
DoSomething();
DoSomething();

Теперь с константами это проще, поэтому давайте посмотрим, как это будет работать с переменной:

for (int i = 0; i < n; i++)
{
    DoSomething();
}

Здесь у вас есть n int сравнений, n приращений и n DoSomethig () вызывает = 3n . Теперь мы не можем развернуть его полностью, но мы можем развернуть его с постоянным коэффициентом (чем выше ожидаемое значение n , тем больше мы должны развернуть его):

int i;
for (i = 0; i < n; i = i+3)
{
    DoSomething();
    DoSomething();
    DoSomething();
}
if (i - n == 2)
{
    DoSomething(); // We passed n by to, so there's one more left
}
else if (i - n == 1)
{
    DoSomething();  //We passed n by only 1, so there's two more left
    DoSomething();
}

Теперь у нас есть Здесь у вас есть n / 3 + 2 int сравнения, n / 3 приращений и n DoSomethig () вызывает = (1 2/3) * n .
Мы спасли себя (1 1/3) * n операций. Что сокращает время вычислений почти вдвое.

К вашему сведению, еще один метод аккуратного развертывания называется Устройство Даффа .
Но это очень специфично для компилятора и конкретной языковой реализации. Есть языки, где это было бы на самом деле хуже.

1 голос
/ 07 октября 2011

когда (на мой взгляд) хорошо развернуть цикл:

Цикл

короткий и, возможно, все используемые переменные находятся в регистре процессора. После развертывания переменные «дублируются», но все еще находятся в регистрах, поэтому штраф памяти или кеша отсутствует.

Цикл

(с неизвестным номером разворачивания цикла) будет выполняться по крайней мере несколько или десятки раз, поэтому есть основания загружать весь цикл, развернутый в кэш инструкций.

если цикл короткий (одно или несколько вторжений), это может быть очень полезно для развертывания, поскольку код для определения необходимости его повторного выполнения выполняется реже.

...