Причина различных скоростей деления с плавающей запятой с и без opt.flag -O3 (C ++ / C) - PullRequest
2 голосов
/ 14 ноября 2011

Я пытался измерить разницу в скорости деления одинарной точности по сравнению с делением двойной точности в C ++

Вот простой код, который я написал.

#include <iostream>
#include <time.h>

int main(int argc, char *argv[])
{

  float     f_x = 45672.0;
  float     f_y = 67783.0;
  double    d_x = 45672.0;
  double    d_y = 67783.0;

  float     f_answer;
  double    d_answer;

  clock_t   start,stop;
  int       N = 200000000 //2*10^8


 start = clock();
 for (int i = 0; i < N; ++i)
  {
    f_answer = f_x/f_y;
  }
 stop = clock();
 std::cout<<"Single Precision:"<< (stop-start)/(double)CLOCKS_PER_SEC<<"    "<<f_answer <<std::endl;


start = clock();
for (int i = 0; i < N; ++i)
  {
    d_answer = d_x/d_y;
  }
stop = clock();
std::cout<<"Double precision:" <<(stop-start)/(double)CLOCKS_PER_SEC<<"   "<< d_answer<<std::endl;

return 0;
}

Когда я скомпилировал код без оптимизации как g++ test.cpp, я получил следующий вывод

Desktop: ./a.out
Single precision:8.06    0.673797
Double precision:12.68   0.673797

Но если я скомпилирую это с g++ -O3 test.cpp, тогда я получу

Desktop: ./a.out
Single precision:0    0.673797
Double precision:0   0.673797

Как я получил такое резкое увеличение производительности? Время, показанное во втором случае, равно 0 из-за низкого разрешения функции clock(). Обнаружил ли компилятор, что каждая итерация цикла for не зависит от предыдущих итераций?

Ответы [ 3 ]

7 голосов
/ 14 ноября 2011

Возможно, потому что компилятор оптимизировал цикл до одной итерации.Возможно, он даже произвел деление во время компиляции.

Проверьте сборщик вашего исполняемого файла, чтобы убедиться (используйте, например, objdump).

5 голосов
/ 15 ноября 2011

Глядя на сборку, полученную из g++ -O3 -S, становится очевидным, что циклы и все ваши вычисления с плавающей запятой (кроме тех, которые связаны со временем) были оптимизированы:

        .section        .text.startup,"ax",@progbits
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB970:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        pushq   %rbx
        .cfi_def_cfa_offset 24
        .cfi_offset 3, -24
        subq    $24, %rsp
        .cfi_def_cfa_offset 48
        call    clock
        movq    %rax, %rbx
        call    clock
        movq    %rax, %rbp
        movl    $.LC0, %esi
        movl    std::cout, %edi
        subq    %rbx, %rbp
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)

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

Вы используете f_answer или d_answer только для распечатки ответа, который может быть тривиально вычислен во время компиляции, и компилятор может это увидеть. Нет смысла даже иметь их. И если нет смысла иметь их, нет смысла иметь либо f_x, f_y, d_x, либо d_y. Все прошло.

Чтобы решить эту проблему, необходимо, чтобы каждая итерация цикла зависела от результатов последней итерации. Вот мое решение этой проблемы. Я использую шаблон complex для выполнения некоторых вычислений, связанных с вычислением набора Mandlebrot:

#include <iostream>
#include <time.h>
#include <complex>

int main(int argc, char *argv[])
{
   using ::std::complex;
   using ::std::cout;

   const complex<float> f_coord(0.1, 0.1);
   const complex<double> d_coord(0.1, 0.1);

   complex<float> f_answer(0, 0);
   complex<double> d_answer(0, 0);

   clock_t   start, stop;
   const unsigned int N = 200000000; //2*10^8

   start = clock();
   for (unsigned int i = 0; i < N; ++i)
   {
      f_answer = (f_answer * f_answer) + f_coord;
   }
   stop = clock();
   cout << "Single Precision: " << (stop-start)/(double)CLOCKS_PER_SEC
        << "    " << f_answer << '\n';


   start = clock();
   for (unsigned int i = 0; i < N; ++i)
   {
      d_answer = (d_answer * d_answer) + d_coord;
   }
   stop = clock();
   cout << "Double precision: " <<(stop-start)/(double)CLOCKS_PER_SEC
        << "   " << d_answer << '\n';

   return 0;
}
1 голос
/ 15 ноября 2011

Если вы добавите квалификатор volatile в определения ваших чисел с плавающей запятой и двойников, компилятор не будет оптимизировать неиспользуемые вычисления.

...