Оптимизируют ли какие-либо компиляторы C или C ++ в рамках определенных макросов? - PullRequest
9 голосов
/ 23 июля 2011

Допустим, у меня есть в C или C ++ следующее:

#include <math.h>
#define ROWS 15
#define COLS 16
#define COEFF 0.15
#define NODES (ROWS*COLS)
#define A_CONSTANT (COEFF*(sqrt(NODES)))

Затем я использую NODES и A_CONSTANT где-то глубоко во многих вложенных циклах (т.е.использовал много раз).Очевидно, что оба имеют числовые значения, которые могут быть определены во время компиляции, но действительно ли компиляторы это делают?Во время выполнения ЦП должен будет оценивать 15*16 каждый раз, когда видит NODES, или компилятор статически помещает туда 240?Точно так же, будет ли ЦП оценивать квадратный корень каждый раз, когда видит A_CONSTANT?

Я предполагаю, что умножение ROWS*COLS оптимизировано, но больше ничего не происходит.Целочисленное умножение встроено в язык, но sqrt является библиотечной функцией.Если это действительно так, есть ли способ получить магическое число, эквивалентное A_CONSTANT, чтобы квадратный корень вычислялся только один раз во время выполнения?

Ответы [ 6 ]

20 голосов
/ 23 июля 2011

Определения макросов расширяются путем простой текстовой подстановки в исходный код, прежде чем он передается собственно компилятору, что может сделать оптимизацию.Компилятор сгенерирует точно такой же код для выражений NODES, ROWS*COLS и 15*16 (и я не могу вспомнить ни одного, который будет выполнять умножение каждый раз в цикле с включенной оптимизацией).

Что касается A_CONSTANT, то, что это макрос снова не имеет значения;важно то, достаточно ли умен компилятор, чтобы понять, что sqrt константы является константой (при условии, что это sqrt из <math.h>).Я знаю, что GCC достаточно умен, и я ожидаю, что и другие компиляторы производственного качества будут достаточно умны.

11 голосов
/ 23 июля 2011

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

9 голосов
/ 23 июля 2011

Это зависит от вашего компилятора.

#include <math.h>

#define FOO sqrt(5);

double
foo()
{
  return FOO;
}

Мой компилятор (gcc 4.1.2) генерирует следующую сборку для этого кода:

.LC0:
    .long   2610427048
    .long   1073865591
    .text
    .p2align 4,,15
.globl foo
    .type   foo, @function
foo:
.LFB2:
    movsd   .LC0(%rip), %xmm0
    ret
.LFE2:

Так что он знает, что sqrt(5) - это константа времени компиляции.

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

8 голосов
/ 23 июля 2011

Здесь действительно два вопроса:

  1. Оптимизирует ли компилятор выражения, найденные внутри макросов?
  2. Оптимизирует ли компилятор sqrt()?

(1) легко: да, это так.Препроцессор отделен от компилятора C и делает свое дело еще до запуска компилятора C.Так что если у вас есть

#define ROWS 15
#define COLS 16
#define NODES (ROWS*COLS)

void foo( ) 
{
   int data[ROWS][COLS];
   printf( "I have %d pieces of data\n", NODES );
   for ( int *i = data; i < data + NODES ; ++i )
   {
     printf("%d ", *i);
   }
}

Компилятор на самом деле увидит:

void foo( ) 
{
   int data[15][16];
   printf( "I have %d pieces of data\n", (15*16) );
   for ( int *i = data; i < data + (15*16) ; ++i )
   {
     printf("%d ", *i);
   }
}

И это зависит от обычной оптимизации констант во время компиляции.

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

Из того, что я видел, MSVC, современные GCC, Intel, IBM и SN считают sqrt внутренним.Старый GCC и некоторые дрянные поставляемые вендором компиляторы для встроенных чипов этого не делают.

3 голосов
/ 23 июля 2011

#defines обрабатываются до компиляции с простой заменой текста.Полученный текстовый файл затем передается на фактический этап компиляции.

Если вы используете gcc, попробуйте скомпилировать исходный файл с переключателем -E, который выполнит предварительную обработку и затем остановится.Посмотрите на сгенерированный файл, чтобы увидеть фактические данные для шага компиляции.

1 голос
/ 23 июля 2011

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

Для сравнения: сравнительно мало достаточно старых компиляторов C ++, чтобы можно было ожидать, что им не понадобится такая оптимизация. Компиляторы, достаточно старые, чтобы не иметь такой простой оптимизации, как правило, будут иметь только C (и даже тогда, не рассчитывайте на это - безусловно, такие вещи, как MS C 5.0 / 5.1 / 6.0, Datalight / Zortech C, Borland и т. Д., Сделали это а также. Насколько я помню, компиляторы C, работавшие на CP / M, в основном этого не делали.

...