Я в настоящее время пишу некоторый glsl как векторные математические классы в C++, и я просто реализовал abs()
функционируйте как это:
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.h
abs
работы внутренне? Различие в производительности является ничем поразительным, но мне просто любопытно!
Поскольку они являются реализацией, они могут делать столько предположений, сколько захотят. Они знают формат 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);
}
Это устраняет ветвление и может работать быстрее.
Здесь может быть несколько причин:
вы уверены, что первый вызов использует std::abs
? С таким же успехом он может использовать целое число abs
из C (либо явно вызывать std::abs
, либо иметь using std::abs;
)
компилятор может иметь внутреннюю реализацию некоторых функций float (например. компилировать их непосредственно в инструкции FPU)
Однако я удивлен, что компилятор не устраняет цикл вообще - поскольку вы не делаете ничего с каким-либо эффектом внутри цикла, и, по крайней мере, в случае abs
, компилятор должен знать, что побочных эффектов нет.
Вероятно, он просто использует битовую маску для установки бита знака в 0.
Вероятно, библиотечная версия abs - это встроенная функция, поведение которой точно известно компилятору, который может даже вычислить значение во время компиляции (поскольку в вашем случае оно известно) и оптимизировать вызов. Вы должны попробовать свой тест со значением, известным только во время выполнения (предоставленным пользователем или полученным с помощью rand () перед двумя циклами).
Если все еще есть разница, то это может быть потому, что библиотека abs написана непосредственно на ручной сборке с помощью магических трюков, поэтому она может быть немного быстрее, чем сгенерированная.
Существуют хорошо известные приемы для вычисления абсолютного значения числа со знаком с дополнением до двух. Если число отрицательное, переверните все биты и добавьте 1, то есть xor с -1 и вычтите -1. Если он положительный, ничего не делать, то есть xor с 0 и вычесть 0.
int my_abs(int x)
{
int s = x >> 31;
return (x ^ s) - s;
}
Ваша версия abs встроена и может быть вычислена один раз, и компилятор может тривиально знать, что возвращаемое значение не изменится, поэтому ему даже не нужно вызывать функцию.
Вам действительно нужно взглянуть на сгенерированный код сборки (установить точку останова и открыть «большой» вид отладчика, эта разборка будет внизу слева, если память не изменяет), а затем вы сможете увидеть, что происходит.
Вы можете без особых проблем найти документацию по вашему процессору в Интернете, она расскажет вам, каковы все инструкции, чтобы вы могли понять, что происходит. Или вставьте его сюда, и мы вам сообщим. ;)
Библиотечная функция abs работает с целыми числами, в то время как вы, очевидно, тестируете числа с плавающей запятой. Это означает, что вызов abs с аргументом float включает преобразование из float в int (может быть нерабочим, поскольку вы используете константу, а компилятор может сделать это во время компиляции), затем операцию INTEGER abs и преобразование int-> float. Ваша шаблонная функция будет включать операции с поплавками, и это, вероятно, имеет значение.
Какой у вас компилятор и какие настройки? Я уверен, что MS и GCC реализуют «внутренние функции» для многих математических и строковых операций.
Следующая строка:
printf("%.3f", abs(1.25));
попадает в следующий путь кода «fabs» (в msvcr90d.dll):
004113DE sub esp,8
004113E1 fld qword ptr [__real@3ff4000000000000 (415748h)]
004113E7 fstp qword ptr [esp]
004113EA call abs (4110FFh)
abs вызывает реализацию «fabs» среды выполнения 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 [__real@3ff0000000000000 (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 [__real@3ff4000000000000 (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)]