Подтвердить что ты не робот

Редко выполняемый и почти пустой, если оператор резко снижает производительность в С++

Уточнение редактора:. Когда это было изначально опубликовано, возникли две проблемы:

  • Эффективность тестирования снижается в три раза, если добавление, казалось бы, несущественного заявления
  • Время, затраченное на завершение теста, по-видимому, меняется случайным образом

Вторая проблема решена: случайность возникает только при работе под отладчиком.

Остальная часть этого вопроса следует понимать как относящуюся к первой отметке выше, и в контексте запуска в режиме VС++ 2010 Express Release с оптимизацией "Максимизировать скорость" и "поддерживать быстрый код".

В разделе комментариев есть комментарии, в которых говорится о втором моменте, но теперь их можно игнорировать.


У меня есть симуляция, где, если я добавлю простой оператор if в цикл while, который запускает фактическое моделирование, производительность падает примерно в три раза (и я выполняю много вычислений в цикле while, n-body gravity для солнечной системы, помимо других вещей), хотя оператор if почти никогда не выполняется:

if (time - cb_last_orbital_update > 5000000)
{
    cb_last_orbital_update = time;
}

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

Переменная time - это время моделирования, она увеличивается с шагом 0,001 в начале, поэтому требуется длительное время, пока оператор if не будет выполнен в первый раз (я также включил печать сообщения, чтобы узнать, выполняется, но это не так, или, по крайней мере, только тогда, когда это предполагается). Несмотря на это, производительность снижается в 3 раза даже в первые минуты симуляции, когда она еще не была выполнена. Если я прокомментирую строку

cb_last_orbital_update = time;

то он снова работает быстрее, поэтому это не проверка на

time - cb_last_orbital_update > 5000000

это определенно простой акт записи текущего времени моделирования в эту переменную.

Кроме того, если я пишу текущее время в другую переменную вместо cb_last_orbital_update, производительность не уменьшается. Таким образом, это может быть проблемой при назначении нового значения переменной, которая используется для проверки выполнения "if"? Это все выстрелы в темноте.

Отказ от ответственности: я довольно новичок в программировании и извиняюсь за весь этот текст.

Я использую Visual С++ 2010 Express, дезактивация функции прекомпилированного заголовка stdafx.h также не имеет значения.

EDIT: базовая структура программы. Обратите внимание, что нигде, кроме того, в конце цикла while (time += time_interval;) не было изменено time. Кроме того, cb_last_orbital_update имеет только 3 вхождения: декларация/инициализация плюс два раза в выражении if, вызывающем проблему.

int main(void)
{
    ...
    double time = 0;
    double time_interval = 0.001;
    double cb_last_orbital_update = 0;

    F_Rocket_Preset(time, time_interval, ...);

    while(conditions)
    {
    Rocket[active].Stage[Rocket[active].r_stage].F_Update_Stage_Performance(time, time_interval, ...);
    Rocket[active].F_Calculate_Aerodynamic_Variables(time);
    Rocket[active].F_Calculate_Gravitational_Forces(cb_mu, cb_pos_d, time);
    Rocket[active].F_Update_Rotation(time, time_interval, ...);
    Rocket[active].F_Update_Position_Velocity(time_interval, time, ...);
    Rocket[active].F_Calculate_Orbital_Elements(cb_mu);
    F_Update_Celestial_Bodies(time, time_interval, ...);

    if (time - cb_last_orbital_update > 5000000.0)
    {
        cb_last_orbital_update = time;
    }

    Rocket[active].F_Check_Apoapsis(time, time_interval);
    Rocket[active].F_Status_Check(time, ...);
    Rocket[active].F_Update_Mass (time_interval, time);
    Rocket[active].F_Staging_Check (time, time_interval);

    time += time_interval;

    if (time > 3.1536E8)
    {
        std::cout << "\n\nBreak main loop! Sim Time: " << time << std::endl;
        break;
    }
    }
...
}

ИЗМЕНИТЬ 2:

Здесь - разница в коде сборки. Слева находится быстрый код с линией

cb_last_orbital_update = time;

скомпилированный, справа - медленный код с линией.

ИЗМЕНИТЬ 4:

Итак, я нашел обходное решение, которое, похоже, прекрасно работает до сих пор:

int cb_orbit_update_counter = 1; // before while loop

if(time - cb_orbit_update_counter * 5E6 > 0)
{
    cb_orbit_update_counter++;
}

ИЗМЕНИТЬ 5:

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

ИЗМЕНИТЬ 6: Извините, это запутывает. Я выследил виновника за более низкую производительность при удалении __declspec(noinline) этой функции, которая выполняется внутри if:

__declspec(noinline) std::string F_Get_Body_Name(int r_body)
{
switch (r_body)
{
case 0:
    {
        return ("the Sun");
    }
case 1:
    {
        return ("Mercury");
    }
case 2:
    {
        return ("Venus");
    }
case 3:
    {
        return ("Earth");
    }
case 4:
    {
        return ("Mars");
    }
case 5:
    {
        return ("Jupiter");
    }
case 6:
    {
        return ("Saturn");
    }
case 7:
    {
        return ("Uranus");
    }
case 8:
    {
        return ("Neptune");
    }
case 9:
    {
        return ("Pluto");
    }
case 10:
    {
        return ("Ceres");
    }
case 11:
    {
        return ("the Moon");
    }
default:
    {
        return ("unnamed body");
    }
}

}

Теперь if делает больше, чем просто увеличивает счетчик:

if(time - cb_orbit_update_counter * 1E7 > 0)
{
    F_Update_Orbital_Elements_Of_Celestial_Bodies(args);
    std::cout << F_Get_Body_Name(3) << " SMA: " << cb_sma[3] << "\tPos Earth: " << cb_pos_d[3][0] << " / " << cb_pos_d[3][1] << " / " << cb_pos_d[3][2] <<
    "\tAlt: " << sqrt(pow(cb_pos_d[3][0] - cb_pos_d[0][0],2) + pow(cb_pos_d[3][1] - cb_pos_d[0][1],2) + pow(cb_pos_d[3][2] - cb_pos_d[0][2],2)) << std::endl;
    std::cout << "Time: " << time << "\tcb_o_h[3]: " << cb_o_h[3] << std::endl;
    cb_orbit_update_counter++;
}

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

ИЗМЕНИТЬ 7: Поэтому я изменил функцию переключателя на

const std::string cb_names[] = {"the Sun","Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto","Ceres","the Moon","unnamed body"}; // global definition
const int cb_number = 12; // global definition

std::string F_Get_Body_Name(int r_body)
{
if (r_body >= 0 && r_body < cb_number)
{
    return (cb_names[r_body]);
}
else
{
    return (cb_names[cb_number]);
}
}

а также сделала еще одну часть кода более тонкой. Теперь программа работает без каких-либо __declspec(noinline). Как предложил ElderBug, проблема с кэшем команд процессора тогда/код становится слишком большим?

4b9b3361

Ответ 1

Я поместил свои деньги на предсказатель Intel. http://en.wikipedia.org/wiki/Branch_predictor

Процессор предполагает (time-cb_last_orbital_update > 5000000) быть ложным большую часть времени и соответствующим образом загружает конвейер выполнения.

После выполнения условия (time-cb_last_orbital_update > 5000000). Задержка ложного предсказания поражает вас. Вы можете потерять 10-20 циклов.

if (time - cb_last_orbital_update > 5000000)
{
    cb_last_orbital_update = time;
}

Ответ 2

Что-то происходит, чего вы не ожидаете.

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

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

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

Ответ 3

Я попробую другое предположение. Это гипотетично, и в основном это связано с компилятором.

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

Решение состоит в том, чтобы добавить __declspec(noinline) во все ваши объявления функций вычислений.

Ответ 4

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

Ответ 5

Обходной путь, попробуйте 2:

Теперь код выглядит следующим образом:

int cb_orbit_update_counter = 1; // before while loop

if(time - cb_orbit_update_counter * 5E6 > 0)
{
    cb_orbit_update_counter++;
}

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

После еще нескольких тестов кажется хорошим.

Ответ 6

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

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