Инструкция компилятора переупорядочивает оптимизации в C ++ (и что их тормозит) - PullRequest
3 голосов
/ 25 февраля 2012

Я сократил свой код до следующего, который настолько прост, насколько я мог бы его сделать, сохранив интересующий меня вывод компилятора.

void foo(const uint64_t used)
{
    uint64_t ar[100];
    for(int i = 0; i < 100; ++i)
    {
        ar[i] = some_global_array[i];
    }

    const uint64_t mask = ar[0];
    if((used & mask) != 0)
    {
        return;
    }

    bar(ar); // Not inlined
}

Использование VC10 с / O2 и / Ob1,сгенерированная сборка в значительной степени отражает порядок инструкций в приведенном выше коде C ++.Поскольку локальный массив ar передается в bar() только в случае сбоя условия и в противном случае не используется, я ожидал бы, что компилятор оптимизируется до чего-то подобного следующему.

if((used & some_global_array[0]) != 0)
{
    return;
}

// Now do the copying to ar and call bar(ar)...

Является ли компиляторне делать этого, потому что для него слишком сложно определить такие оптимизации в общем случае?Или он следует строгому правилу, запрещающему это делать?Если так, то почему, и есть ли какой-то способ, которым я могу дать подсказку, что это не изменит семантику моей программы?

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

Ответы [ 2 ]

3 голосов
/ 25 февраля 2012

Вероятно, причиной того, что это не оптимизируется, является глобальный массив. Компилятор не может знать заранее, если, скажем, доступ к some_global_array[99] приведет к генерации какого-то исключения / сигнала, поэтому он должен выполнить весь цикл. Все было бы иначе, если бы глобальный массив был статически определен в одной и той же единице компиляции.

Например, в LLVM следующие три определения глобального массива приведут к сильно отличающимся выводам этой функции:

// this yields pretty much what you're seeing
uint64_t *some_global_array; 
// this calls memcpy and then performs the conditional check
uint64_t some_global_array[100] = {0};
// this calls memset (not memcpy!) on the ar array and then bar directly (no 
// conditional checks since the array is const and filled with 0s, so the if
// is always false) 
const uint64_t some_global_array[100] = {0};

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

1 голос
/ 25 февраля 2012

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

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

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