#c #compiler-optimization
#c #компилятор-оптимизация
Вопрос:
У меня есть цикл, подобный приведенному ниже, который имеет инвариант, здесь никогда не меняющееся значение scaleEveryValueByTwo
. Могу ли я полагаться на компилятор, который находит этот инвариант и не проверяет условие на каждой итерации (по сути, компилирует во что-то аналогичное коду внизу)?
void loadValuesFromDisk(const bool scaleEveryValueByTwo)
{
std::vector<MyValueType> xs;
while(fileHasNewValues())
{
auto x = loadNextValue();
if (scaleEveryValueByTwo)
{
x *= 2;
}
xs.push_back(x);
}
}
Я, конечно, могу разделить это на два цикла вручную (см. Ниже) или поместить часть масштабирования в отдельную функцию, но во многих случаях это делает код намного длиннее и, на мой взгляд, сложнее для чтения (например, если у меня есть вложенные циклы для всех измерений 3D-данных, я бы продублировал все тристроки заголовков цикла и до шести строк фигурных скобок).
void loadValuesFromDisk(const bool scaleEveryValueByTwo)
{
std::vector<MyValueType> xs;
while(fileHasNewValues())
{
auto x = loadNextValue();
xs.push_back(x);
}
if (scaleEveryValueByTwo)
{
for(auto amp;x : xs)
{
x *= 2;
}
}
}
Меня в первую очередь интересует, могу ли я полагаться на эту (или, что еще лучше, применять) эту оптимизацию для часто используемых компиляторов, таких как gcc или MSVC, а не на какие-то экзотические, в которых может отсутствовать оптимизация, которая де-факто является стандартной в большинстве компиляторов.
Комментарии:
1. Скорее всего, так и будет. Даже если этого не произойдет, предсказатель ветвления быстро изучит его, и производительность будет такой же.
2. Общее эмпирическое правило заключается в том, что компилятор сделает все оптимизации, о которых вы можете подумать, и кучу других, которые вы даже не ожидали.
3. @NathanOliver правильно… Компилятор, скорее всего, выполнит эту оптимизацию, если это хорошая идея . Задавать такого рода вопросы вне контекста результата профилирования почти наверняка является преждевременной оптимизацией (т. Е. Неправильным вопросом).
Ответ №1:
Ранее в компиляторе MSVC использовался /Og (глобальная оптимизация), которые теперь включены по умолчанию. Я предполагаю, что другие компиляторы также делают это.
Чтобы узнать, как выполняется оптимизация цикла, перейдите по ссылке ниже и найдите «Оптимизация цикла»
https://docs.microsoft.com/en-us/cpp/build/reference/og-global-optimizations?view=vs-2019
Поскольку теперь это происходит по умолчанию, вы можете положиться на компилятор.
Комментарии:
1. Это навело меня на правильный путь, у других компиляторов, похоже, есть похожие опции, например
-fmove-loop-invariants
, и несколько более агрессивных для gcc .
Ответ №2:
Вы можете создать scaleEveryValueByTwo
параметр шаблона, чтобы быть уверенным, что условие вычисляется только один раз. В C 17 вы можете использовать if constexpr
следующее
template <bool scaleEveryValueByTwo>
void loadValuesFromDisk()
{
std::vector<MyValueType> xs;
while(fileHasNewValues())
{
auto x = loadNextValue();
if constexpr (scaleEveryValueByTwo)
{
x *= 2;
}
xs.push_back(x);
}
}
Если у вас еще нет C 17, приведенный выше код можно получить, например, с помощью вспомогательной шаблонной функции multiply
следующим образом
template <bool activate>
void multiply(decltype(loadNextValue())amp; x);
template <>
void multiply<true>(decltype(loadNextValue())amp; x) { x *= 2; }
template <>
void multiply<false>(decltype(loadNextValue())amp; x) { }
template <bool scaleEveryValueByTwo>
void loadValuesFromDisk()
{
std::vector<MyValueType> xs;
while(fileHasNewValues())
{
auto x = loadNextValue();
multiply<scaleEveryValueByTwo>(x);
xs.push_back(x);
}
}
(Примечание: я использую decltype
, потому что не знаю, что loadNextValue()
возвращает ваша процедура.)
Затем вы вызываете либо loadValuesFromDisk<true>()
или loadValuesFromDisk<false>()
. Если scaleEveryValueByTwo
известно только во время выполнения, вы можете перейти к соответствующей функции:
void loadValuesFromDisk(bool const scaleEveryValueByTwo)
{
if (scaleEveryValueByTwo)
loadValuesFromDisk<true>();
else
loadValuesFromDisk<false>();
}
Комментарии:
1. Это
if constexpr
не требуется для случая, предшествующего C 17.if (/* pure constexpr constant */) {
компилятор определенно оптимизирует.