Ответ 1
Да, если определено, что флаг не изменяется и не может быть изменен do_something или do_something_else, его можно вытащить за пределы цикла. Я слышал об этом названии петли, но в Википедии есть запись
Рассмотрим пример:
if (flag)
for (condition)
do_something();
else
for (condition)
do_something_else();
Если flag
не изменяется внутри циклов for
, это должно быть семантически эквивалентно:
for (condition)
if (flag)
do_something();
else
do_something_else();
Только в первом случае код может быть намного длиннее (например, если используется несколько циклов for
или если do_something()
является кодовым блоком, который в основном идентичен do_something_else()
), а во втором случае, флаг проверяется много раз.
Мне любопытно, смогут ли текущие компиляторы С++ (а главное g++) оптимизировать второй пример, чтобы избавиться от повторных тестов внутри цикла for
. Если да, то при каких условиях это возможно?
Да, если определено, что флаг не изменяется и не может быть изменен do_something или do_something_else, его можно вытащить за пределы цикла. Я слышал об этом названии петли, но в Википедии есть запись
Пробовал GCC и -O3:
void foo();
void bar();
int main()
{
bool doesnt_change = true;
for (int i = 0; i != 3; ++i) {
if (doesnt_change) {
foo();
}
else {
bar();
}
}
}
Результат для основного:
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call ___main
call __Z3foov
call __Z3foov
call __Z3foov
xorl %eax, %eax
leave
ret
Таким образом, он оптимизирует выбор (и разворачивает меньшие циклы).
Эта оптимизация не выполняется, если doesnt_change является глобальным.
Я уверен, что компилятор может определить, что флаг останется постоянным, он может сделать несколько shufflling:
const bool flag = /* ... */;
for (..;..;..;)
{
if (flag)
{
// ...
}
else
{
// ...
}
}
Если flag
не const
, компилятор не может оптимизировать цикл, потому что он не может быть уверен, что flag
не изменится. Может быть, если он делает статический анализ, но не все компиляторы, я думаю. const
- это верный способ сообщить компилятору, что флаг не изменится, после чего он до компилятора.
Как обычно, профиль и выясните, действительно ли это проблема.
Как правило, да. Но нет никакой гарантии, и места, где это сделает компилятор, вероятно, редки.
То, что большинство компиляторов делает без проблем, - это вытащить неизменяемые оценки из цикла, например. если ваше состояние
if (a<b) ....
когда цикл a не влияет на a и b, сравнение будет выполняться один раз перед циклом.
Это означает, что если компилятор может определить условие, это не изменится, тест будет дешевым, и предсказание будет предсказано. Это, в свою очередь, означает, что сам тест стоит одного цикла или вообще нет цикла (действительно).
В каких случаях расщепление цикла было бы полезным?
a) очень плотный цикл, где 1 цикл является значительной стоимостью б) весь цикл с обеими частями не соответствует кешу кода
Теперь компилятор может делать только предположения о кеше кода и обычно может заказывать код так, чтобы одна ветка соответствовала кешу.
Без какого-либо тестирования I'dexpect a) единственный случай, когда такая оптимизация будет применена, потому что это всегда лучший выбор:
В каких случаях расщепление цикла было бы плохим?
При расщеплении цикла увеличивается размер кода за пределами кеша кода, вы получите значительный успех. Теперь это влияет только на то, если сам цикл вызывается в другом цикле, но это то, что компилятор обычно не может определить.
[править]
Я не мог заставить VC9 разбить следующий цикл (один из немногих случаев, когда это может быть полезно)
extern volatile int vflag = 0;
int foo(int count)
{
int sum = 0;
int flag = vflag;
for(int i=0; i<count; ++i)
{
if (flag)
sum += i;
else
sum -= i;
}
return sum;
}
[edit 2]
обратите внимание, что при int flag = true;
вторая ветвь будет оптимизирована. (и нет, const не имеет значения здесь;))
Что это значит? Либо это не поддерживает это, это не имеет значения, ro мой анализ неправильный;-)
В общем, я бы предпочел, что это оптимизация, которая ценна только в очень немногих случаях и может быть легко выполнена в большинстве сценариев.
Как многие говорили: это зависит.
Если вы хотите быть уверенным, вы должны попытаться применить решение для компиляции. Шаблоны часто пригождаются для этого:
for (condition)
do_it<flag>();
Он называется инвариантом цикла, и оптимизация называется движением кодового инвариантного кода, а также подъемом кода. Тот факт, что он в условном случае определенно сделает анализ кода более сложным, и компилятор может или не может инвертировать цикл и условное в зависимости от того, насколько умен оптимизатор.
Существует общий ответ для любого конкретного случая такого рода вопросов, а также для компиляции вашей программы и просмотра сгенерированного кода.
Я бы с осторожностью сказал, что так будет. Может ли он гарантировать, что значение не будет изменено этим или другим потоком?
Тем не менее, вторая версия кода, как правило, более читабельна и, вероятно, последняя вещь должна быть оптимизирована в блоке кода.