17

我目前正在用 C++ 编写一些类似向量数学类的 glsl,我刚刚实现了一个abs()这样的函数:

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

我将它的速度与默认的 C++ 进行了比较absmath.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 大约需要 25 毫秒,而我的需要 60 毫秒。我想正在进行一些低级别的优化。有人知道math.h abs内部是如何工作的吗?性能差异并不显着,但我只是好奇!

4

8 回答 8

20

由于它们是实现,因此它们可以自由地做出尽可能多的假设。他们知道 的格式,double并且可以用它来玩花样。

可能(几乎没有问题),你doublebinary64 格式。这意味着符号有自己的位,绝对值只是清除该位。例如,作为一种特化,编译器实现者可以执行以下操作

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

这消除了分支并可能运行得更快。

于 2010-05-20T21:48:53.907 回答
10

计算二进制补码有符号数的绝对值有一些众所周知的技巧。如果数字为负数,则翻转所有位并加 1,即与 -1 异或并减去 -1。如果是正数,什么也不做,即与 0 异或减去 0。

int my_abs(int x)
{
    int s = x >> 31;
    return (x ^ s) - s;
}
于 2010-05-20T22:04:35.960 回答
9

你的编译器和设置是什么?我确信 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 >= 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)] 
于 2010-05-20T22:28:45.883 回答
4

它可能只是使用位掩码将符号位设置为 0。

于 2010-05-20T21:46:46.610 回答
3

可能有几件事:

  • 你确定第一个电话使用std::abs吗?它也可以使用 C 中的整数abs(显式调用std::abs或具有using std::abs;

  • 编译器可能具有某些浮点函数的内在实现(例如,将它们直接编译成 FPU 指令)

然而,令我惊讶的是编译器并没有完全消除循环——因为你没有在循环内做任何有任何影响的事情,至少在 的情况下abs,编译器应该知道没有副作用。

于 2010-05-20T21:50:09.760 回答
3

您的 abs 版本是内联的,可以计算一次,编译器可以很容易地知道返回的值不会改变,所以它甚至不需要调用函数。

您确实需要查看生成的汇编代码(设置断点,并打开“大”调试器视图,如果内存服务,此反汇编将在左下角),然后您可以看到发生了什么。

您可以毫不费力地在线找到有关处理器的文档,它会告诉您所有说明是什么,以便您弄清楚发生了什么。或者,将其粘贴在这里,我们会告诉你。;)

于 2010-05-20T22:11:22.730 回答
1

可能 abs 的库版本是一个内在函数,其行为完全由编译器知道,它甚至可以在编译时计算值(因为在你的情况下它是已知的)并优化调用。您应该使用仅在运行时已知的值(由用户提供或在两个周期之前使用 rand() 获得)尝试您的基准测试。

如果还有区别,可能是因为库 abs 是直接用魔术技巧手工锻造的汇编编写的,所以它可能比生成的快一点。

于 2010-05-20T21:46:45.260 回答
0

The library abs function operates on integers while you are obviously testing floats. This means that call to abs with float argument involves conversion from float to int (may be a no-op as you are using constant and compiler may do it at compile time), then INTEGER abs operation and conversion int->float. You templated function will involve operations on floats and this is probably making a difference.

于 2010-05-21T06:54:06.887 回答