我有以下代码,并期望使用exp()函数的内部版本。不幸的是,它不在x64构建中,这使得它比类似的Win32 (即32位构建)慢:
#include "stdafx.h"
#include <cmath>
#include <intrin.h>
#include <iostream>
int main()
{
const int NUM_ITERATIONS=10000000;
double expNum=0.00001;
double result=0.0;
for (double i=0;i<NUM_ITERATIONS;++i)
{
result+=exp(expNum); // <-- The code of interest is here
expNum+=0.00001;
}
// To prevent the above from getting optimized out...
std::cout << result << '\n';
}我在我的构建中使用了以下开关:
/Zi /nologo /W3 /WX-
/Ox /Ob2 /Oi /Ot /Oy /GL /D "WIN32" /D "NDEBUG"
/D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm-
/EHsc /GS /Gy /arch:SSE2 /fp:fast /Zc:wchar_t /Zc:forScope
/Yu"StdAfx.h" /Fp"x64\Release\exp.pch" /FAcs /Fa"x64\Release\"
/Fo"x64\Release\" /Fd"x64\Release\vc100.pdb" /Gd /errorReport:queue 正如您所看到的,我有/Oi、/O2和/fp:fast,这是每个MSDN关于本质的文章所必需的。然而,尽管我做出了努力,还是调用了标准库,使得exp()在x64构建上执行得更慢。
下面是生成的程序集:
for (double i=0;i<NUM_ITERATIONS;++i)
000000013F911030 movsd xmm10,mmword ptr [__real@3ff0000000000000 (13F912248h)]
000000013F911039 movapd xmm8,xmm6
000000013F91103E movapd xmm7,xmm9
000000013F911043 movaps xmmword ptr [rsp+20h],xmm11
000000013F911049 movsd xmm11,mmword ptr [__real@416312d000000000 (13F912240h)]
{
result+=exp(expNum);
000000013F911052 movapd xmm0,xmm7
000000013F911056 call exp (13F911A98h) // ***** exp lib call is here *****
000000013F91105B addsd xmm8,xmm10
expNum+=0.00001;
000000013F911060 addsd xmm7,xmm9
000000013F911065 comisd xmm8,xmm11
000000013F91106A addsd xmm6,xmm0
000000013F91106E jb main+52h (13F911052h)
}正如您在上面的程序集中所看到的,有一个对exp()函数的调用。现在,让我们看看为32位构建的for循环生成的代码:
for (double i=0;i<NUM_ITERATIONS;++i)
00101031 xorps xmm1,xmm1
00101034 rdtsc
00101036 push ebx
00101037 push esi
00101038 movsd mmword ptr [esp+1Ch],xmm0
0010103E movsd xmm0,mmword ptr [__real@3ee4f8b588e368f1 (102188h)]
00101046 push edi
00101047 mov ebx,eax
00101049 mov dword ptr [esp+3Ch],edx
0010104D movsd mmword ptr [esp+28h],xmm0
00101053 movsd mmword ptr [esp+30h],xmm1
00101059 lea esp,[esp]
{
result+=exp(expNum);
00101060 call __libm_sse2_exp (101EC0h) // <--- Quite different from 64-bit
00101065 addsd xmm0,mmword ptr [esp+20h]
0010106B movsd xmm1,mmword ptr [esp+30h]
00101071 addsd xmm1,mmword ptr [__real@3ff0000000000000 (102180h)]
00101079 movsd xmm2,mmword ptr [__real@416312d000000000 (102178h)]
00101081 comisd xmm2,xmm1
00101085 movsd mmword ptr [esp+20h],xmm0
expNum+=0.00001;
0010108B movsd xmm0,mmword ptr [esp+28h]
00101091 addsd xmm0,mmword ptr [__real@3ee4f8b588e368f1 (102188h)]
00101099 movsd mmword ptr [esp+28h],xmm0
0010109F movsd mmword ptr [esp+30h],xmm1
001010A5 ja wmain+40h (101060h)
}那里有更多的代码,但速度更快。我在3.3 GHz Nehalem-EP主机上进行的定时测试得出了以下结果:
32位:
循环体平均执行时间: 34.849229周期/ 10.560373 ns
64位:
循环体平均执行时间: 45.845323周期/ 13.892522 ns
确实是很奇怪的行为。为什么会这样?
更新:
我创建了一个Microsoft bug报告。可以随意修改它,以便从微软自己那里得到一个关于浮点本质用法的权威答案,特别是在x64代码中。
发布于 2012-04-10 20:30:30
在x64上,使用SSE实现浮点算法。这没有针对exp()的内置操作,因此,除非您自己手动编写内联向量化__m128d exp(__m128d) (用SSE实现指数函数的最快实现),否则调用标准库是不可避免的。
我想,您所指的MSDN文章是用32位代码编写的,它使用的是8087 FP。
发布于 2013-05-24 13:26:47
我认为微软提供32位SSE2 exp()内部版本的唯一原因是标准的调用约定。32位调用约定要求将操作数推到主堆栈上,并在FPU堆栈的顶级寄存器中返回结果。如果启用了SSE2代码生成,则可能会将返回值从FPU堆栈弹出到内存中,然后从该位置加载到SSE2寄存器中,以便对结果执行任何数学操作。显然,在SSE2寄存器中传递操作数并在SSE2寄存器中返回结果更快。这就是__libm_sse2_exp()所做的。在64位代码中,标准的调用约定将传递操作数并在SSE2寄存器中返回结果,因此拥有内部版本没有好处。
32位SSE2和64位exp()实现性能差异的原因是微软在这两种实现中使用了不同的算法。我不知道他们为什么要这么做,他们对某些操作数产生不同的结果(不同的1 1ulp)。
发布于 2012-04-10 20:26:58
我想在讨论中添加到x64指令集手册和英特尔参考资料的链接。
在最初的检查中,应该有一种使用F2XM1计算指数的方法。但是,它在x87指令集隐藏在x64模式。中
有希望显式地使用MMX/x87,正如在VirtualDub讨论板。上的一篇文章所描述的,这是如何在VC++中实际编写asm。
https://stackoverflow.com/questions/10095465
复制相似问题