Почему sqrt стал намного быстрее без -O2 в g++ на моем компьютере?

Рассмотрим следующий код:

#include <cstdio>
#include <cmath>

const int COUNT = 1000000000;

int main()
{
    double sum = 0;
    for (int i = 1; i <= COUNT; ++i) {
        sum += sqrt(i);
    }
    printf("%f\n", sum);
    return 0;
}

Без -O2 он работает только на 2.9 с на моем компьютере, тогда как он работает с 6,4 с -O2.

Мой компьютер Fedora 23 с g++ 5.3.1.

Я пробовал то же самое на Ubuntu 14.04 (с g++ 4.8), у него нет проблемы (все 6.4s).

Ответ 1

В версии Naive используется функция вызова функции glibc sqrt.

Оптимизированная версия использует инструкцию SSE sqrtsd. Но после завершения инструкции он проверяет, что значение результата не является NaN. Если значением результата является NaN, то он вызывает функцию glibc sqrt для установки правильных флагов ошибок (см. Страницу руководства для math_error(7)). См. Почему компилятор генерирует дополнительные sqrts в скомпилированном ассемблере для подробного объяснения.

Почему gcc думает, что это быстрее? Никто не знает. Если вы уверены, что ваши номера не генерируют NaN, используйте параметр -fno-math-errno compile.

Ответ 2

Исследование сборки может вызвать некоторые ответы, но самый простой способ увидеть разницу в коде - сделать -fdump-tree-optimized. Проблема, похоже, связана с перегрузками sqrt, а именно с предоставлением библиотеки C sqrt(double) и С++ 11 sqrt(int). Последнее кажется более быстрым, и GCC, похоже, не заботится о том, используете ли вы -std=c++11 или префикс std:: до sqrt или нет.

Здесь выдержка для дампа с -O2 или -O (-O без номера включает оптимизацию, чтобы отключить все оптимизации, опустить -O):

  int i;
  double sum;
  double _9;
  __type _10;

  <bb 2>:

  <bb 3>:
  # sum_15 = PHI <sum_6(3), 0.0(2)>
  # i_16 = PHI <i_7(3), 1(2)>
  _9 = (double) i_16;
  _10 = __builtin_sqrt (_9);
  sum_6 = _10 + sum_15;
  i_7 = i_16 + 1;
  if (i_7 == 1000000001)
    goto <bb 4>;
  else
    goto <bb 3>;

Тогда без -O2:

  <bb 4>:
  _8 = std::sqrt<int> (i_2);
  sum_9 = sum_1 + _8; 
  i_10 = i_2 + 1; 
  goto <bb 3>; 

Обратите внимание, что он использует std::sqrt<int>. Для скептического ответа см. Почему sqrt в глобальной области действия намного медленнее, чем std:: sqrt в MinGW?