- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
为了学术目的,我在玩C ++中的基本数学函数实现。今天,我对平方根的以下代码进行了基准测试:
inline float sqrt_new(float n)
{
__asm {
fld n
fsqrt
}
}
sqrt
函数快(它花费了标准函数执行时间的85%左右)。
#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;
}
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函数调用或FSQRT指令之外,还有许多瓶颈。并且比最佳标量SQRTSS(单精度)代码至少慢2倍。这可能比像样的SSE2矢量化循环所能达到的速度慢8倍。即使不重新排序任何数学运算,您也可以击败SQRTSS吞吐量。
https://gcc.gnu.org/wiki/DontUseInlineAsm中的许多原因都适用于您的示例。编译器将无法通过函数传播常量,并且它将不知道结果总是非负的(如果不是NaN)。如果您以后将数字平方,它也将无法将其优化为fabs()
。
同样非常重要的是,您要使用SSE2 SQRTPS(_mm_sqrt_ps()
)击败自动矢量化。使用内在函数的“无错误检查”标量sqrt()函数也存在该问题。 IDK是否可以在没有/fp:fast
的情况下获得最佳结果,但我对此表示怀疑。 (除了在汇编中编写整个循环,或者自己使用内部函数向量化整个循环)。
您的Haswell CPU设法以最快的速度运行函数调用循环,这令人印象深刻,尽管inline-asm循环甚至可能也不会使FSQRT吞吐量饱和。
由于某种原因,您的库函数调用正在调用double sqrt(double)
,而不是C ++重载float sqrt(float)
。这导致转换为双精度,然后又返回为浮点型。可能您需要#include <cmath>
to get the overloads,或者可以调用sqrtf()
。 Linux上的gcc和clang用您当前的代码调用sqrtf()(无需转换为double和back),但是也许它们的<random>
标头恰好包含了<cmath>
,而MSVC却没有。也许还有其他事情正在发生。
库函数调用循环将总和保留在内存中(而不是寄存器中)。显然,__libm_sse2_sqrt_precise
的32位版本使用的调用约定不会保留任何XMM寄存器。 Windows x64 ABI确实保留XMM6-XMM15 but wikipedia says this is new and the 32-bit ABI didn't do that。我假设如果有任何保留呼叫的XMM寄存器,则MSVC的优化器将利用它们。
无论如何,除了在每个独立的标量浮点数上调用sqrt的吞吐量瓶颈之外,对sqrt1的循环承载依赖性是一个延迟瓶颈,其中包括存储转发往返:
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
00091290 movss xmm0,dword ptr [eax] # load from the array
00091294 movss dword ptr [esp+6Ch],xmm0 # store to the stack
0009129A fld dword ptr [esp+6Ch] # x87 load from stack
0009129E fsqrt
000912A0 fstp dword ptr [esp+6Ch] # x87 store to the stack
000912A4 movss xmm0,dword ptr [esp+6Ch] # SSE load from the stack (of sqrt(array[i]))
000912AA add eax,4
000912AD addss xmm0,dword ptr [esp+54h] # SSE load+add of the sum
000912B3 movss dword ptr [esp+54h],xmm0 # SSE store of the sum
/fp:fast
,则如果结果为NaN,则
sqrtf()
库函数必须设置
errno
。这就是为什么它不能仅内联到SQRTSS。
// DON'T USE THIS, it defeats auto-vectorization
static inline
float sqrt_scalar(float x) {
__m128 xvec = _mm_set_ss(x);
xvec = _mm_cvtss_f32(_mm_sqrt_ss(xvec));
}
-ffast-math
)的近似最佳标量循环。在
the Godbolt compiler explorer上查看:
# gcc6.2 -O3 for the sqrt_new loop using _mm_sqrt_ss. good scalar code, but don't optimize further.
.L10:
movss xmm0, DWORD PTR [r12]
add r12, 4
sqrtss xmm0, xmm0
addss xmm1, xmm0
cmp r12, rbx
jne .L10
-O3
)使用SQRTSS甚至不检查NaN。我认为这是合法的,而不是编译器错误。也许看到代码没有使用errno?
# gcc6.2's sqrt() loop, without -ffast-math.
# speculative inlining of SQRTSS with a check + fallback
# spills/reloads a lot of stuff to memory even when it skips the call :(
# xmm1 = 0.0 (gcc -fverbose-asm says it's holding sqrt2, which is zero-initialized, so I guess gcc decides to reuse that zero)
.L9:
movss xmm0, DWORD PTR [rbx]
sqrtss xmm5, xmm0
ucomiss xmm1, xmm0 # compare input against 0.0
movss DWORD PTR [rsp+8], xmm5
jbe .L8 # if(0.0 <= SQRTSS input || unordered(0.0, input)) { skip the function call; }
movss DWORD PTR [rsp+12], xmm1 # silly gcc, this store isn't needed. ucomiss doesn't modify xmm1
call sqrtf # called for negative inputs, but not for NaN.
movss xmm1, DWORD PTR [rsp+12]
.L8:
movss xmm4, DWORD PTR [rsp+4] # silly gcc always stores/reloads both, instead of putting the stores/reloads inside the block that the jbe skips
addss xmm4, DWORD PTR [rsp+8]
add rbx, 4
movss DWORD PTR [rsp+4], xmm4
cmp rbp, rbx
jne .L9
/fp:fast
或
-ffast-math
,即使像clang这样的聪明编译器也无法将
_mm_sqrt_ss
重写为SQRTPS。 Clang通常不仅在将内在函数映射到指令1:1方面非常擅长,而且如果您错过了将事物组合的机会,它还会提供更优化的混洗和混合。
_mm_sqrt_ss
是一个很大的损失。 clang将
sqrt()
库函数调用版本编译为RSQRTPS +牛顿拉普森迭代。
sqrt_new()
实现的延迟不敏感,而对吞吐量不敏感。延迟通常在真实的FP代码中很重要,而不仅仅是吞吐量。但是在其他情况下,就像独立地对许多数组元素执行相同的操作一样,延迟并不重要,因为无序执行可以通过让许多循环迭代中的指令在运行中将其隐藏得足够好。
fld n
并非直接来自数组。
关于performance - 了解为什么ASM fsqrt实现比标准sqrt函数更快,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39631457/
我在玩(美丽的)多项式 x^4 - 10x^2 + 1 . 看看会发生什么: In[46]:= f[x_] := x^4 - 10x^2 + 1 a = Sqrt[2];
对于整个参数范围 x,a >= 0,是否有一种优雅的数值稳定评估以下表达式的方法? f(x,a) = sqrt(x+a) - sqrt(x) 还有没有提供这种功能的任何编程语言或库?如果是,以什么名义
我正在制作自定义运算符 (≠,≈,Σ,√),平方根的实现很有趣。我写的 prefix func √ (item:Double) -> Double { return sqrt(item) }
这是我每秒调用多次的函数: static inline double calculate_scale(double n) { //n may be int or double return s
我对 C++ 很陌生,我有这段代码,代码如下所示: D = (sum_B / double(E))*std::sqrt(E) 有人可以将其解释为数学公式或易于理解的东西吗,我不确定这是什么 std::
这个问题在这里已经有了答案: Can a declaration affect the std namespace? (2 个答案) Why doesn't adding sqrt() cause
考虑以下代码: #include #include const int COUNT = 100000000; int main() { double sum = 0; for (i
x**(1/2)、math.sqrt() 和 cmath.sqrt() 有什么区别? 为什么 cmath.sqrt() 单独得到二次项的复根?我应该专门将它用于我的平方根吗?他们在后台做了什么不同的事
我创建了一个小程序,如下: #include #include #include int main(int argc, char *argv[]) { int i;
我得到了如下表达式(Sqrt[XXX] 的数量未知) Sqrt[A+B] + Sqrt[Min[A,B]] * Min[Sqrt[C],D] 我想把所有的 Sqrt[XXX] 变成 Sqrt(XXX)
这次重复: T(n) = sqrt(n) * T(sqrt(n)) + n 它似乎无法用 Master 定理求解。它似乎也无法用 Akra-Bazzi 解决。即使我设置 n = 2^k 以便 T(2^
在数学中,恒等式 (1 + sqrt(2))^2 = 3 + 2*sqrt(2) 成立。但在浮点(IEEE 754,使用单精度,即 32 位)计算中,情况并非如此,因为 sqrt(2) 没有精确的二进
我创建了一个小程序,如下: #include #include #include int main(int argc, char *argv[]) { int i;
这给了我 0: int B=-4; double A = Math.Sqrt(1/B); 但是这个 NaN double A = Math.Sqrt(-4); 第一次计算怎么可能不失败或者至少不返回
template T sqrt (T); template complex sqrt(complex); double sqrt(double); void f(complex z) { sq
这里是 Python 的新手。我试图了解此函数如何检查素数: from itertools import count, islice from math import sqrt def is_prim
上述复杂性的大 O 表示法是什么? 是不是O(n) 最佳答案 是的。关键是日志里面的平方根没有区别: O(sqrt(n) log(sqrt(n))) = O(sqrt(n) 1/2 log(n)) =
如果 g(n) = sqrt(n)sqrt(n),g(n) = O(2n) 的复杂度是多少? 感谢任何帮助。 最佳答案 比较两个指数函数时,一个有用的技巧是让它们具有相同的基数: √n√n = (2l
我正在查看我的代码,希望提高它的性能,然后我看到了这个: int sqrt = (int) Math.floor(Math.sqrt(n)); 哦,好的,我真的不需要调用 Math.floor,因为转
我试图找到满足子句 (x - y * √ 2016)/(y + √ 2016) = 2016 的数字。数字 x 和 y 可以是有理数。 这是我已经尝试过的: #include #include #
我是一名优秀的程序员,十分优秀!