Странное различие в производительности C++?

Я просто наткнулся на изменение, которое, кажется, имеет парадоксальные разветвления производительности. Кто-либо может дать возможное объяснение этого поведения?

Исходный код:

for (int i = 0; i < ct; ++i) {
    // do some stuff...

    int iFreq = getFreq(i);
    double dFreq = iFreq;

    if (iFreq != 0) {
        // do some stuff with iFreq...
        // do some calculations with dFreq...
    }
}

В то время как чистка этого кода во время "выступления передает", я решил переместить определение dFreq в if блок, поскольку это только использовалось в if. Существует несколько вовлечения вычислений dFreq таким образом, я не устранил его полностью, поскольку это действительно сохраняет стоимость нескольких преобразований во время выполнения из int кому: double. Я не ожидал различия в производительности, или если любой вообще, незначительное улучшение. Однако производительность, уменьшенная почти на 10%. Я много раз измерял это, и это - действительно единственное изменение, которое я внес. Фрагмент кода, показанный выше, выполняет внутреннюю пару других циклов. Я получаю очень последовательные синхронизации через выполнения и могу определенно подтвердить, что изменение описываю выполнение уменьшений на ~10%. Я ожидал бы, что производительность увеличится потому что int кому: double преобразование только произошло бы когда iFreq != 0.

Код Chnaged:

for (int i = 0; i < ct; ++i) {
    // do some stuff...

    int iFreq = getFreq(i);

    if (iFreq != 0) {
        // do some stuff with iFreq...
        double dFreq = iFreq;
        // do some stuff with dFreq...
    }
}

Кто-либо может объяснить это? Я использую VC ++ 9.0 с/O2. Я просто хочу понять то, что я не объясняю здесь.

6
задан Earlz 26 March 2010 в 03:30
поделиться

8 ответов

Вы должны поместить преобразование в dFreq непосредственно в if () перед выполнением вычислений с iFreq. Преобразование может выполняться параллельно с целочисленными вычислениями, если инструкция находится дальше в коде. Хороший компилятор мог бы продвинуть его дальше, а не очень хороший может просто оставить его там, где он упал. Поскольку вы переместили его после целочисленных вычислений, он может не работать параллельно с целочисленным кодом, что приведет к замедлению. Если он работает параллельно, то в зависимости от ЦП улучшения могут быть незначительными или вообще отсутствовать (выполнение инструкции FP, результат которой никогда не используется, будет иметь небольшой эффект в исходной версии).

Если вы действительно хотите повысить производительность, некоторые люди выполнили тесты и оценили следующие компиляторы в следующем порядке:

1) ICC - компилятор Intel 2) GCC - хорошее второе место { {1}} 3) Код, сгенерированный MSVC, может быть довольно плохим по сравнению с остальными.

Вы также можете попробовать -O3, если он у них есть.

7
ответ дан 8 December 2019 в 04:29
поделиться

Возможно, результатом getFreq будет хранится в регистре в первом случае и записывается в память во втором случае? Также может быть, что снижение производительности связано с механизмами ЦП, такими как конвейерная обработка и / или прогнозирование ветвлений. Вы можете проверить сгенерированный код сборки.

6
ответ дан 8 December 2019 в 04:29
поделиться

По-моему, это похоже на остановку трубопровода

int iFreq = getFreq(i);
    double dFreq = iFreq;

    if (iFreq != 0) {

Позволяет преобразование в двойное параллельно с другим кодом. так как dFreq используется не сразу. Это дает компилятору что-то. между хранением iFreq и его использованием, так что это преобразование, скорее всего. "Свободный".

Но

int iFreq = getFreq(i);

if (iFreq != 0) {
    // do some stuff with iFreq...
    double dFreq = iFreq;
    // do some stuff with dFreq...
}

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

Современные процессоры могут делать несколько вещей за тактовый цикл, но только когда вещи независимы. Две последовательные инструкции, которые ссылаются на один и тот же регистр, часто приводят к остановке. Фактическое преобразование в двойник может занять 3 часа, но все, кроме первых часов, можно делать параллельно с другими работами, при условии, что вы не ссылаетесь на результат преобразования для одной или двух инструкций.

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

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

Было бы интересно посмотреть, какие инструкции на самом деле выдал компилятор для этих двух случаев.

4
ответ дан 8 December 2019 в 04:29
поделиться

Попробуйте перенести определение dFreq за пределы цикла for, но сохраните задание внутри блока for loop/if.

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

double dFreq;
int iFreq;
for (int i = 0; i < ct; ++i) 
{
    // do some stuff...

    iFreq = getFreq(i);

    if (iFreq != 0) 
    {
        // do some stuff with iFreq...
        dFreq = iFreq;
        // do some stuff with dFreq...
    }
} 
3
ответ дан 8 December 2019 в 04:29
поделиться

возможно, компилятор оптимизирует его, выводя определение за пределы цикла for. когда вы помещаете его в if, оптимизация компилятора этого не делает.

2
ответ дан 8 December 2019 в 04:29
поделиться

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

1
ответ дан 8 December 2019 в 04:29
поделиться

Это достаточно легко узнать. Просто сделайте 20 стеков медленной версии и быстрой версии. В медленной версии вы увидите примерно на двух кадрах то, что она делает, чего не делает в быстрой версии. Вы увидите небольшую разницу в том, где он останавливается на языке ассемблера.

1
ответ дан 8 December 2019 в 04:29
поделиться

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

В этой статье (немного старой, но вполне достоверной) говорится (со статистикой) нечто подобное: http://www.tantalon.com/pete/cppopt/asyougo.htm#PostponeVariableDeclaration

1
ответ дан 8 December 2019 в 04:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: