2010-05-20 5 views
15

В настоящее время я пишу некоторые GLSL как вектор математических классов в C++, и я просто реализовал abs() функцию следующим образом:Чем отличается C++ math.h абс() по сравнению с моей абс()

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

Я сравнил его скорость по умолчанию C++ в 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.habs? Разница в производительности ничего драматичного, но мне просто любопытно!

+3

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

+2

'abs' принимает целое число и возвращает целое число. Поэтому использование 'abs' просто неверно в вашем сравнении. Вместо этого используйте 'fabs' из' ''. – kennytm

+0

И, пожалуйста, расскажите нам, что является вашим компилятором/окружением. – jpalecek

ответ

1

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

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

4

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

+0

эй, я не очень опытен в побитовое программирование, как это будет работать? – moka

+0

@moka: См. Http://en.wikipedia.org/wiki/Double_precision_floating-point_format. –

15

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

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

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); 
} 

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

+0

Не будет ли это вопиющим примером неопределенного поведения? – jpalecek

+5

@jpalecek: Я не вижу там UB. 'reinterpret_cast' имеет поведение, определенное реализацией. (Отсюда моя точка зрения, «они могут делать любые предположения для конкретной реализации, которые они хотят, поскольку они являются реализацией».) Небиблиотечные кодеры не могут. Когда вы напишете этот код, вы принимаете определенные вещи о своей реализации. – GManNickG

+1

Если двойной не IEEE-754, да. Это, по крайней мере, менее портативный –

3

Там может быть несколько вещей:

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

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

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

8

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

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

значения с плавающей запятой не сохраняются в двух дополнениях, но это умный трюк для абсолютного значения. (Для определения знака я делаю тот же первый шаг, но затем возвращаю 's | 1'; -1 = отрицательный, +1 = положительный). –

+0

Это хорошо, хотя не по теме ... – jpalecek

+0

@jpal Достаточно честный, но где именно мока утверждает, что его интересуют только типы с плавающей точкой? Может быть, 'template ' должен быть заменен на 'template ' then, чтобы быть понятным? – fredoverflow

1

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

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

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

8

Какой у вас компилятор и настройки? Я уверен, что 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) 

абс вызывать реализацию С RUNTIME '' на систему FABS 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 такт цикла onl у на FPU> = Pentium), то dissasembly выход:

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)] 
1

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