Оптимизируется ли логическое условие в цикле for, которое всегда ложно? - PullRequest
4 голосов
/ 27 ноября 2009

У меня следующая ситуация

bool user_set_flag;

getFlagFromUser(&user_set_flag);

while(1){

    if(user_set_flag){
        //do some computation and output

    }


    //do other computation
}

Переменная user_set_flag устанавливается в коде только один раз и только один раз, в самом начале, по сути, пользователь выбирает, что он хочет делать с программой. Скажем, что пользователь выбирает user_set_flag = false, и компилятор скомпилирует код таким образом, что оператор if(user_set_flag) будет проверен только один раз или всегда. Могу ли я дать подсказки компилятору, такие как установка bool в const?

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

Ответы [ 7 ]

14 голосов
/ 27 ноября 2009

Во-первых, процессоры имеют возможность, называемую прогнозирование ветвления . После нескольких запусков цикла процессор сможет заметить, что ваш оператор if всегда идет в одну сторону. (Он может даже заметить регулярные паттерны, такие как true false true false.) Затем он спекулятивно выполнит эту ветвь, и при условии, что сможет правильно предсказать, дополнительные затраты на if утверждение в значительной степени устранено. Если вы считаете, что пользователь с большей вероятностью выберет true, а не false, вы даже можете сообщить об этом компилятору gcc (расширение, специфичное для gcc).

Однако вы упомянули в одном из своих комментариев, что у вас есть «гораздо более сложная последовательность bools». Я думаю, возможно, что у процессора нет памяти для сопоставления с образцом всех этих переходов - к тому времени, когда он возвращается к первому оператору if, знание того, каким образом этот переход прошел, смещено с его объем памяти. Но мы могли бы помочь здесь ...

Компилятор имеет возможность преобразовывать циклы и операторы if в то, что он считает более оптимальными формами. Например. он может преобразовать ваш код в форму, заданную schnaader. Это известно как отключение контура . Вы можете помочь в этом, выполнив Оптимизация по профилю (PGO) , сообщив компилятору, где находятся горячие точки. (Примечание: в gcc -funswitch-loops включается только при -O3.)

Вы должны профиль свой код на уровне инструкций ( VTune будет хорошим инструментом для этого), чтобы увидеть, действительно ли операторы if являются узким местом. Если это действительно так, и, глядя на сгенерированную сборку, вы думаете, что компилятор ошибся, несмотря на PGO, вы можете попробовать самостоятельно вывести оператор if. Возможно, шаблонный код сделает это более удобным:

template<bool B> void innerLoop() {
    for (int i=0; i<10000; i++) {
        if (B) {
            // some stuff..
        } else {
            // some other stuff..
        }
    }
}
if (user_set_flag) innerLoop<true>();
else innerLoop<false>();
6 голосов
/ 27 ноября 2009

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

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

В качестве упражнения попытайтесь профилировать (время) выполнение, используя if (true) и if(user_set_flag). Я предполагаю, что во времени выполнения будет нулевая разница.

5 голосов
/ 27 ноября 2009

Альтернативой будет:

if(user_set_flag){
    while(1){
      ComputationAndOutput();
      OtherComputation();
    }
} else {
    while(1){
      OtherComputation();
    }
}

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

4 голосов
/ 27 ноября 2009

Технически компилятор может оптимизировать подобные ситуации.

Например:

#include <cstdio>

int main(int argc, char* [])
{
    while (true)
    {
        if (argc == 1) {
            puts("one");
        }
        puts("some more");
    }
}

main компилируется в (G ++ -O3):

    cmpl    $1, 8(%ebp)
    je  L9
    .p2align 4,,15
L2:
    movl    $LC1, (%esp)
    call    _puts
    jmp L2
L9:
    movl    $LC0, (%esp)
    call    _puts
    movl    $LC1, (%esp)
    call    _puts
    movl    $LC0, (%esp)
    call    _puts
    movl    $LC1, (%esp)
    call    _puts
    jmp L9

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

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

1 голос
/ 27 ноября 2009

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

В общем, компилятор может "оптимизировать" только те вещи, которые он знает во время компиляции . Что означает: в данный момент вы нажали пункт «Сборка» в вашем меню вашего редактора. Если оно может измениться, его - обычно - нельзя оптимизировать.

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

0 голосов
/ 27 ноября 2009

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

Это робко.

Вместо этого возьмите на себя ответственность. Это показывает, как.

0 голосов
/ 27 ноября 2009

Если вы знаете значение флага во время компиляции, вы можете добавить флаг компиляции, чтобы не включать оператор if как:

while(1){
   #ifdef user_set_flag
    {
        //do some computation and output

    }
   #endif


    //do other computation
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...