Что же касается С++ math.h abs() по сравнению с моим абс()

В настоящее время я пишу некоторые классные классы, такие как векторные математические классы в С++, и я только что реализовал функцию abs() следующим образом:

template<class T>
static inline T abs(T _a)
{
    return _a < 0 ? -_a : _a;
}

Я сравнил его скорость с С++ abs по умолчанию от math.h следующим образом:

clock_t begin = clock();
for(int i=0; i<10000000; ++i)
{
    float a = abs(-1.25);
};

clock_t end = clock();
unsigned long time1 = (unsigned long)((float)(end-begin) / ((float)CLOCKS_PER_SEC/1000.0));

begin = clock();
for(int i=0; i<10000000; ++i)
{
    float a  = myMath::abs(-1.25);
};
end = clock();
unsigned long time2 = (unsigned long)((float)(end-begin) / ((float)CLOCKS_PER_SEC/1000.0));

std::cout<<time1<<std::endl;
std::cout<<time2<<std::endl;

Теперь абзац по умолчанию занимает около 25 мс, а мой занимает 60. Я предполагаю, что происходит некоторая оптимизация низкого уровня. Кто-нибудь знает, как math.h abs работает внутри? Разница в производительности ничего драматичного, но мне просто интересно!

Ответы

Ответ 1

Поскольку они являются реализацией, они могут делать столько предположений, сколько захотят. Они знают формат double и могут вместо этого играть с трюками.

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

template <>
double abs<double>(const double x)
{
    // breaks strict aliasing, but compiler writer knows this behavior for the platform
    uint64_t i = reinterpret_cast<const std::uint64_t&>(x);
    i &= 0x7FFFFFFFFFFFFFFFULL; // clear sign bit

    return reinterpret_cast<const double&>(i);
}

Это удаляет ветвление и может работать быстрее.

Ответ 2

Существуют общеизвестные трюки для вычисления абсолютного значения знакового числа с двумя дополнениями. Если число отрицательное, переверните все биты и добавьте 1, то есть xor с -1 и вычтите -1. Если он положительный, ничего не делайте, то есть xor с 0 и вычитайте 0.

int my_abs(int x)
{
    int s = x >> 31;
    return (x ^ s) - s;
}

Ответ 3

Каков ваш компилятор и настройки? Я уверен, что MS и GCC реализуют "встроенные функции" для многих математических и строковых операций.

Следующая строка:

printf("%.3f", abs(1.25));

попадает в следующий путь кода "fabs" (в msvcr90d.dll):

004113DE  sub         esp,8 
004113E1  fld         qword ptr [[email protected] (415748h)] 
004113E7  fstp        qword ptr [esp] 
004113EA  call        abs (4110FFh) 

abs вызывает реализацию исполняемых файлов C на MSVCR90D (довольно большой):

102F5730  mov         edi,edi 
102F5732  push        ebp  
102F5733  mov         ebp,esp 
102F5735  sub         esp,14h 
102F5738  fldz             
102F573A  fstp        qword ptr [result] 
102F573D  push        0FFFFh 
102F5742  push        133Fh 
102F5747  call        _ctrlfp (102F6140h) 
102F574C  add         esp,8 
102F574F  mov         dword ptr [savedcw],eax 
102F5752  movzx       eax,word ptr [ebp+0Eh] 
102F5756  and         eax,7FF0h 
102F575B  cmp         eax,7FF0h 
102F5760  jne         fabs+0D2h (102F5802h) 
102F5766  sub         esp,8 
102F5769  fld         qword ptr [x] 
102F576C  fstp        qword ptr [esp] 
102F576F  call        _sptype (102F9710h) 
102F5774  add         esp,8 
102F5777  mov         dword ptr [ebp-14h],eax 
102F577A  cmp         dword ptr [ebp-14h],1 
102F577E  je          fabs+5Eh (102F578Eh) 
102F5780  cmp         dword ptr [ebp-14h],2 
102F5784  je          fabs+77h (102F57A7h) 
102F5786  cmp         dword ptr [ebp-14h],3 
102F578A  je          fabs+8Fh (102F57BFh) 
102F578C  jmp         fabs+0A8h (102F57D8h) 
102F578E  push        0FFFFh 
102F5793  mov         ecx,dword ptr [savedcw] 
102F5796  push        ecx  
102F5797  call        _ctrlfp (102F6140h) 
102F579C  add         esp,8 
102F579F  fld         qword ptr [x] 
102F57A2  jmp         fabs+0F8h (102F5828h) 
102F57A7  push        0FFFFh 
102F57AC  mov         edx,dword ptr [savedcw] 
102F57AF  push        edx  
102F57B0  call        _ctrlfp (102F6140h) 
102F57B5  add         esp,8 
102F57B8  fld         qword ptr [x] 
102F57BB  fchs             
102F57BD  jmp         fabs+0F8h (102F5828h) 
102F57BF  mov         eax,dword ptr [savedcw] 
102F57C2  push        eax  
102F57C3  sub         esp,8 
102F57C6  fld         qword ptr [x] 
102F57C9  fstp        qword ptr [esp] 
102F57CC  push        15h  
102F57CE  call        _handle_qnan1 (102F98C0h) 
102F57D3  add         esp,10h 
102F57D6  jmp         fabs+0F8h (102F5828h) 
102F57D8  mov         ecx,dword ptr [savedcw] 
102F57DB  push        ecx  
102F57DC  fld         qword ptr [x] 
102F57DF  fadd        qword ptr [[email protected] (1022CF68h)] 
102F57E5  sub         esp,8 
102F57E8  fstp        qword ptr [esp] 
102F57EB  sub         esp,8 
102F57EE  fld         qword ptr [x] 
102F57F1  fstp        qword ptr [esp] 
102F57F4  push        15h  
102F57F6  push        8    
102F57F8  call        _except1 (102F99B0h) 
102F57FD  add         esp,1Ch 
102F5800  jmp         fabs+0F8h (102F5828h) 
102F5802  mov         edx,dword ptr [ebp+0Ch] 
102F5805  and         edx,7FFFFFFFh 
102F580B  mov         dword ptr [ebp-0Ch],edx 
102F580E  mov         eax,dword ptr [x] 
102F5811  mov         dword ptr [result],eax 
102F5814  push        0FFFFh 
102F5819  mov         ecx,dword ptr [savedcw] 
102F581C  push        ecx  
102F581D  call        _ctrlfp (102F6140h) 
102F5822  add         esp,8 
102F5825  fld         qword ptr [result] 
102F5828  mov         esp,ebp 
102F582A  pop         ebp  
102F582B  ret   

В режиме деблокирования вместо этого используется команда FPU FABS (выполняется 1 такт только на FPU >= Pentium), выход разборки:

00401006  fld         qword ptr [[email protected] (402100h)] 
0040100C  sub         esp,8 
0040100F  fabs             
00401011  fstp        qword ptr [esp] 
00401014  push        offset string "%.3f" (4020F4h) 
00401019  call        dword ptr [__imp__printf (4020A0h)] 

Ответ 4

Возможно, он просто использует битовую маску для установки знакового бита в 0.

Ответ 5

Может быть несколько вещей:

  • Вы уверены, что первый вызов использует std::abs? Он мог бы также использовать целое число abs из C (либо вызвать std::abs явно, либо иметь using std::abs;)

  • у компилятора может быть встроенная реализация некоторых функций с плавающей запятой (например, скомпилировать их непосредственно в инструкции FPU)

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

Ответ 6

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

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

Ответ 7

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

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

Вы можете найти документацию на своем процессоре онлайн без особых проблем, она расскажет вам, что все инструкции, чтобы вы могли понять, что происходит. Кроме того, вставьте его здесь, и мы скажем вам.;)

Ответ 8

Функция абзаца библиотеки работает с целыми числами, в то время как вы, очевидно, тестируете поплавки. Это означает, что вызов abs с аргументом float включает преобразование из float в int (может быть no-op, поскольку вы используете константу, а компилятор может это сделать во время компиляции), затем INTEGER abs operation and conversion int- > float. Функция шаблона будет включать операции с поплавками, и это, вероятно, имеет значение.