Компилятор С++ оптимизирует переданные аргументы

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

WARN(
     "Danger Will Robinson! There are "
     + boost::lexical_cast<string>(minutes)
     + " minutes of oxygen left!"
);

Я использую встроенную функцию для WARN, но мне любопытно, сколько оптимизации происходит за кулисами — оценка аргументов во всей программе будет дорогостоящей. Функция WARN выглядит примерно так:

bool WARNINGS_ENABLED = false;
inline void WARN(const string &message) {
    if (!WARNINGS_ENABLED) {
       return;
    }
    // ...
}

Учитывая, что построение строкового аргумента не имеет побочных эффектов, оптимизирует ли его компилятор? Требуется ли определенный уровень оптимизации (-Ox в g++ для некоторых x)?


person cdleary    schedule 12.11.2008    source источник


Ответы (5)


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

Что вам нужно, так это переименовать вашу функцию в WARN2 и добавить макрос, например:

#define WARN(s) do {if (WARNINGS_ENABLED) WARN2(s);} while (false)

Это предотвратит оценку s во время выполнения, если у вас не включены предупреждения.

Использование do-while — это уловка, которая позволяет использовать его в любом месте кода (голый оператор, оператор в блоке if, заключенном в фигурные скобки, оператор в блоке if без фигурных скобок, операторы while в фигурных скобках и без фигурных скобок и т. д.).

person paxdiablo    schedule 12.11.2008
comment
Да, ясная ситуация, когда макросы нужно использовать вместо встроенных функций. - person cdleary; 12.11.2008
comment
+1, но на самом деле это должно быть так: #define WARN(s) do { if (WARNINGS_ENABLED) WARN2(s); } в то время как (ложь) - person Greg Rogers; 12.11.2008
comment
@ Том, ты прав, этого не будет, но ты читал код спрашивающих? Он говорит, что встроенная пустота. - person paxdiablo; 12.11.2008
comment
@ Грег, спасибо за это, я пытался вспомнить трюк, чтобы убедиться, что код работает внутри / вне фигурных скобок / без фигурных скобок операторов if / while / do. Включено в ответ. - person paxdiablo; 12.11.2008
comment
Это одно из тех очевидных решений, что и делает его блестящим. Я просто пытаюсь найти недостатки в его приложении общего назначения. - person Tom Leys; 12.11.2008
comment
@ Том, я думаю, что если есть возвращаемое значение, оператор больше не свободен от побочных эффектов, поскольку где-то хранит retval - в этом случае вы не можете использовать if, чтобы остановить его выполнение. - person paxdiablo; 12.11.2008
comment
@ Том, как насчет #define WARN(r,s) do { if (WARN_ENAB) r = WARN2(s) else r = 0; } while (false), если WARN2 обычно возвращает 0, если предупреждения отключены? - person paxdiablo; 12.11.2008
comment
Если вам нужна версия, которая возвращает значение, будет работать что-то вроде следующего: #define WARN(s) (WARNING_ENABLED ? WARN2(s) : someDefaultValue) - person Michael Burr; 12.11.2008
comment
@MikeB, это намного лучше, чем мой кладж. - person paxdiablo; 12.11.2008

Вы можете проверить, что делает GCC/G++, используя параметр -S. Это выведет код до того, как он будет фактически собран — см. gcc(1).

GCC и G++ в этом случае ведут себя более или менее одинаково. Поэтому я сначала перевел код на C, чтобы провести дополнительные тесты:

char WARNINGS_ENABLED = 0;

inline void WARN(const char* message) {
    if (!WARNINGS_ENABLED) {
        return;
    }
    puts(message);
}

int main() {
    WARN("foo");
    return 0;
}

запустите gcc -O3 -S file.c и просмотрите выходной файл 'file.s'
. Вы увидите, что GCC не удалил что угодно!

Это не то, о чем вы просили, но чтобы дать компилятору возможность оптимизировать этот код, вам нужно сделать WARNINGS_ENABLED константой. В качестве альтернативы можно сделать его статическим и не изменять значение в этом файле. Но: создание статического имеет побочный эффект, заключающийся в том, что символ не экспортируется.

static const char WARNINGS_ENABLED = 0;

inline void WARN(const char* message) {
  if (!WARNINGS_ENABLED) {
      return;
  }
  puts(message);
}

int main() {
    WARN("foo");
    return 0;
}

Затем GCC полностью очищает код.

person Benedikt Waldvogel    schedule 12.11.2008
comment
Очень хороший ответ! К сожалению, поведением ведения журнала должна быть возможность управлять во время выполнения. - person cdleary; 12.11.2008

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

Я не эксперт по повышению, но я предполагаю, что есть способ построить лямбду, которая будет оцениваться для генерации строки только в том случае, если WARNINGS_ENABLED истинно. Что-то вроде...

inline void warnFunc(some_boost_lambda &message_generator) {
  if (WARNINGS_ENABLED) {
    cerr << message_generator() << endl;
  }
}

#define WARN(msg) warnFunc(...insert boost magic here to turn msg into a lambda...)
person Mr Fooz    schedule 12.11.2008
comment
да, ваша идея отлично работает. только что проверил это с помощью boost:: lambda (тоже не эксперт в этом, но это было прямолинейно) - person Johannes Schaub - litb; 12.11.2008
comment
да, ваша идея отлично работает. только что протестировал его с помощью boost:: lambda (тоже не эксперт в этом, но это было прямолинейно). Вот пример с выводом: codepad.org/PmUh7AHj - person Johannes Schaub - litb; 12.11.2008
comment
Хорошая работа litb - однако это не полное решение (пока), потому что вы не доказали, когда оценивается лямбда, и вы не скрывали лямбду от пользователя. - person Tom Leys; 12.11.2008
comment
Правильно. очень жаль. еще одно место, где лямбды С++ 1x будут крутиться :) - person Johannes Schaub - litb; 12.11.2008
comment
#define LOGPARAM(...) []() -› string { return VA_ARGS; } \n template‹typename T› void log(T t) { if(WARNINGS_ENABLED) cerr ‹‹ t(); } \n ... log(LOGPARAM(Опасность: + lexical_cast‹string›(42))); ... \n yay работает на лямбда-ветке gcc :) - person Johannes Schaub - litb; 12.11.2008

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

Кстати, если WARN является встроенной функцией, вы все равно будете платить за построение сообщения (что очень неэффективно в вашем примере с lexical_cast и operator+ для строк), даже если оно отключено.

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

person ididak    schedule 12.11.2008

Разве вы не можете просто определить все это с помощью препроцессора?

void inline void LogWarning(const string &message) 
{
  //Warning
}

#ifdef WARNINGS_ENABLED
#define WARN(a) LogWarning(a)
#else
#define WARN(a)
#endif

Именно так работает макрос ASSERT(). Весь код в скобках в WARN даже не проходит через препроцессор в компилятор. Это означает, что вы можете делать другие вещи, такие как

#ifdef WARNINGS_ENABLED
// Extra setup for warning
#endif
//....
WARN(uses setup variables)

И он будет компилироваться в обоих направлениях.

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

person Tom Leys    schedule 12.11.2008
comment
Он должен иметь возможность переключения во время выполнения, но это, безусловно, было бы возможным решением во время компиляции. - person cdleary; 12.11.2008
comment
Итак, ваш вопрос: учитывая функцию X (params) {if (b) {quit};...} компилятор откладывает вычисление параметров до тех пор, пока не будет выполнено if? Это было бы впечатляюще. Поскольку ветвь является динамической, компилятор вряд ли сможет ее оптимизировать. - person Tom Leys; 12.11.2008
comment
Ага, это я и спрашиваю! За исключением того, что компилятор может избавиться от вызова и встроить материал в ваш .... Как указывали некоторые другие люди, компилятор должен быть в состоянии доказать, что при передаче аргументов нет внешних побочных эффектов, что кажется довольно сложным. - person cdleary; 12.11.2008