Что же касается С++ 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. Функция шаблона будет включать операции с поплавками, и это, вероятно, имеет значение.