Могут ли компиляторы автоматически оптимизировать повторные вызовы математических функций?

Скажем, у меня был этот фрагмент кода:

#include <cmath>

// ...

float f = rand();
std::cout << sin(f) << " " << sin(f);

Поскольку sin(f) - хорошо определенная функция, существует простая оптимизация:

float f = rand();
float sin_f = sin(f);
std::cout << sin_f << " " << sin_f;

Это оптимизация, которую разумно ожидать от современного компилятора С++? Или нет способа для компилятора определить, что sin(f) всегда должен возвращать одно и то же значение для равного значения f?

Ответы

Ответ 1

Использование g++, построенного с использованием флагов оптимизации по умолчанию:

float f = rand();
40117e: e8 75 01 00 00          call   4012f8 <_rand>
401183: 89 44 24 1c             mov    %eax,0x1c(%esp)
401187: db 44 24 1c             fildl  0x1c(%esp)
40118b: d9 5c 24 2c             fstps  0x2c(%esp)
std::cout << sin(f) << " " << sin(f);
40118f: d9 44 24 2c             flds   0x2c(%esp)
401193: dd 1c 24                fstpl  (%esp)
401196: e8 65 01 00 00          call   401300 <_sin>  <----- 1st call
40119b: dd 5c 24 10             fstpl  0x10(%esp)
40119f: d9 44 24 2c             flds   0x2c(%esp)
4011a3: dd 1c 24                fstpl  (%esp)
4011a6: e8 55 01 00 00          call   401300 <_sin>  <----- 2nd call
4011ab: dd 5c 24 04             fstpl  0x4(%esp)
4011af: c7 04 24 e8 60 40 00    movl   $0x4060e8,(%esp)

Построено с помощью -O2:

float f = rand();
4011af: e8 24 01 00 00          call   4012d8 <_rand>
4011b4: 89 44 24 1c             mov    %eax,0x1c(%esp)
4011b8: db 44 24 1c             fildl  0x1c(%esp)
std::cout << sin(f) << " " << sin(f);
4011bc: dd 1c 24                fstpl  (%esp)
4011bf: e8 1c 01 00 00          call   4012e0 <_sin>  <----- 1 call

Из этого можно видеть, что без оптимизации компилятор использует 2 вызова и только 1 с оптимизациями, эмпирически, я думаю, мы можем сказать, что компилятор оптимизирует вызов.

Ответ 2

Я достаточно уверен, что метки GCC sin имеют нестандартный чистый атрибут, т.е. __attribute__ ((pure));

Это имеет следующий эффект:

Многие функции не имеют эффектов, кроме возвращаемого значения, а их возвращаемое значение зависит только от параметров и/или глобальных переменных. Такая функция может быть подвержена общему исключению подвыражения и оптимизации цикла, так же, как и арифметический оператор.

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

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

(обновление: на самом деле cmath использует constexpr, что подразумевает ту же оптимизацию)