【发布时间】:2016-09-22 06:08:19
【问题描述】:
出于学术目的,我一直在使用 C++ 中的基本数学函数实现。今天,我对平方根的以下代码进行了基准测试:
inline float sqrt_new(float n)
{
__asm {
fld n
fsqrt
}
}
我惊讶地发现它始终比标准的 sqrt 函数快(它需要标准函数的大约 85% 的执行时间)。
我不太明白为什么,很想更好地理解它。下面我展示了我用来分析的完整代码(在 Visual Studio 2015 中,在发布模式下编译并打开所有优化):
#include <iostream>
#include <random>
#include <chrono>
#define M 1000000
float ranfloats[M];
using namespace std;
inline float sqrt_new(float n)
{
__asm {
fld n
fsqrt
}
}
int main()
{
default_random_engine randomGenerator(time(0));
uniform_real_distribution<float> diceroll(0.0f , 1.0f);
chrono::high_resolution_clock::time_point start1, start2;
chrono::high_resolution_clock::time_point end1, end2;
float sqrt1 = 0;
float sqrt2 = 0;
for (int i = 0; i<M; i++) ranfloats[i] = diceroll(randomGenerator);
start1 = std::chrono::high_resolution_clock::now();
for (int i = 0; i<M; i++) sqrt1 += sqrt(ranfloats[i]);
end1 = std::chrono::high_resolution_clock::now();
start2 = std::chrono::high_resolution_clock::now();
for (int i = 0; i<M; i++) sqrt2 += sqrt_new(ranfloats[i]);
end2 = std::chrono::high_resolution_clock::now();
auto time1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();
auto time2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();
cout << "Time elapsed for SQRT1: " << time1 << " seconds" << endl;
cout << "Time elapsed for SQRT2: " << time2 << " seconds" << endl;
cout << "Average of Time for SQRT2 / Time for SQRT1: " << time2 / time1 << endl;
cout << "Equal to standard sqrt? " << (sqrt1 == sqrt2) << endl;
system("pause");
return 0;
}
编辑:我正在编辑问题以包含两个循环的反汇编代码,它们在 Visual Studio 2015 中计算平方根。
首先,for (int i = 0; i<M; i++) sqrt1 += sqrt(ranfloats[i]);的反汇编:
00091194 0F 5A C0 cvtps2pd xmm0,xmm0
00091197 E8 F2 18 00 00 call __libm_sse2_sqrt_precise (092A8Eh)
0009119C F2 0F 5A C0 cvtsd2ss xmm0,xmm0
000911A0 83 C6 04 add esi,4
000911A3 F3 0F 58 44 24 4C addss xmm0,dword ptr [esp+4Ch]
000911A9 F3 0F 11 44 24 4C movss dword ptr [esp+4Ch],xmm0
000911AF 81 FE 90 5C 46 00 cmp esi,offset __dyn_tls_dtor_callback (0465C90h)
000911B5 7C D9 jl main+190h (091190h)
接下来是for (int i = 0; i<M; i++) sqrt2 += sqrt_new(ranfloats[i]);的反汇编:
00091290 F3 0F 10 00 movss xmm0,dword ptr [eax]
00091294 F3 0F 11 44 24 6C movss dword ptr [esp+6Ch],xmm0
0009129A D9 44 24 6C fld dword ptr [esp+6Ch]
0009129E D9 FA fsqrt
000912A0 D9 5C 24 6C fstp dword ptr [esp+6Ch]
000912A4 F3 0F 10 44 24 6C movss xmm0,dword ptr [esp+6Ch]
000912AA 83 C0 04 add eax,4
000912AD F3 0F 58 44 24 54 addss xmm0,dword ptr [esp+54h]
000912B3 F3 0F 11 44 24 54 movss dword ptr [esp+54h],xmm0
000912B9 ?? ?? ??
000912BA ?? ?? ??
000912BB ?? ?? ??
000912BC ?? ?? ??
000912BD ?? ?? ??
000912BE ?? ?? ??
000912BF ?? ?? ??
000912C0 ?? ?? ??
000912C1 ?? ?? ??
000912C2 ?? ?? ??
000912C3 ?? ?? ??
000912C4 ?? ?? ??
000912C5 ?? ?? ??
000912C6 ?? ?? ??
000912C7 ?? ?? ??
000912C8 ?? ?? ??
000912C9 ?? ?? ??
000912CA ?? ?? ??
000912CB ?? ?? ??
000912CC ?? ?? ??
000912CD ?? ?? ??
000912CE ?? ?? ??
000912CF ?? ?? ??
000912D0 ?? ?? ??
000912D1 ?? ?? ??
000912D2 ?? ?? ??
000912D3 ?? ?? ??
000912D4 ?? ?? ??
000912D5 ?? ?? ??
000912D6 ?? ?? ??
000912D7 ?? ?? ??
000912D8 ?? ?? ??
000912D9 ?? ?? ??
000912DA ?? ?? ??
000912DB ?? ?? ??
000912DC ?? ?? ??
000912DD ?? ?? ??
000912DE ?? ?? ??
【问题讨论】:
-
其中一个原因可能是标准的 sqrt 来自链接库,因此它不能被内联并且容易出现calling overhead,而您的实现是完全可内联的。
-
我在我的电脑上得到了相同的时间。使用
/fp:fast选项可使库版本的速度几乎提高一倍。 -
我想如果我真的想知道答案,我会输出汇编程序 (/FAs) 并检查代码。
-
@Sergey 不,我没有。但实际上,由于仍然很少有关于发生了什么的线索,我认为最好的办法实际上是用完整的反汇编来编辑问题
-
/fp:fast通常没有那么危险,除非您出于某种原因特别选择了操作顺序,或者您希望遇到 NaN 并且仍然可以获得有用的结果。例如如果您知道您的数组在大数和小数之间交替,并且您不希望自动矢量化最终仅将小数相加,直到它们大到足以大于大数总和的 1 ulp。或者,如果您需要在不同编译器版本的不同构建中精确重现输出。
标签: performance visual-c++ sqrt