我目前正在用C ++编写一些类似矢量数学类的glsl,我刚刚实现了abs()
这样的函数:
template<class T>
static inline T abs(T _a)
{
return _a < 0 ? -_a : _a;
}
我将其速度与abs
的默认C ++ 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;
现在默认的abs需要大约25ms而我的需要60.我猜有一些低级别的优化正在进行中。有人知道math.h
abs
在内部如何运作吗?性能差异并不显着,但我只是好奇!
答案 0 :(得分:16)
由于它们是实现,因此可以自由地进行任意数量的假设。他们知道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);
}
这会删除分支,可能会运行得更快。
答案 1 :(得分:9)
有一些众所周知的技巧来计算二进制补码的绝对值。如果数字为负数,则翻转所有位并加1,即xor为-1并减去-1。如果是正数,则不执行任何操作,即xor为0并减去0。
int my_abs(int x)
{
int s = x >> 31;
return (x ^ s) - s;
}
答案 2 :(得分:8)
您的编译器和设置是什么?我确信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在MSVCR90D上调用C运行时'fabs'实现(相当大):
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指令(仅在FPU&gt; = Pentium上需要1个时钟周期),解集输出为:
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)]
答案 3 :(得分:4)
它可能只是使用位掩码将符号位设置为0。
答案 4 :(得分:3)
可能有以下几点:
您确定第一个电话使用std::abs
吗?它也可以使用C中的整数abs
(明确地调用std::abs
,或者using std::abs;
)
编译器可能具有一些浮点函数的内部实现(例如,将它们直接编译为FPU指令)
然而,我很惊讶编译器并没有完全消除循环 - 因为你没有对循环中的任何影响做任何事情,至少在abs
的情况下,编译器应该知道那里没有副作用。
答案 5 :(得分:1)
abs的库版本可能是一个内部函数,它的行为完全为编译器所知,它甚至可以在编译时计算值(因为在你的情况下它是已知的)并优化调用。您应该使用仅在运行时知道的值(由用户提供或在两个周期之前使用rand())尝试您的基准测试。
如果仍然存在差异,可能是因为库abs直接用手工伪造的装配用魔术技巧编写,所以它可能比生成的快一点。
答案 6 :(得分:1)
你的abs版本是内联的,可以计算一次,编译器可以很容易地知道返回的值不会改变,所以它甚至不需要调用函数。
你真的需要查看生成的汇编代码(设置一个断点,并打开“大”调试器视图,如果内存服务,这个反汇编将在左下角),然后你就可以看到发生了什么。 / p>
您可以在线查找处理器上的文档而不会有太多麻烦,它会告诉您所有说明是什么,以便您可以弄清楚发生了什么。或者,将它粘贴在这里,我们会告诉你。 ;)
答案 7 :(得分:1)
库abs函数对整数进行操作,而您显然正在测试浮点数。这意味着使用float参数调用abs涉及从float转换为int(可能是无操作,因为您使用常量,编译器可能在编译时执行此操作),然后INTEGER abs操作和转换int-&gt; float。你模板化的函数将涉及对浮点数的操作,这可能会产生影响。