双精度值存储更高的精度,并且是浮点数的两倍,但是Intel CPU是否针对浮点数进行了优化?
也就是说,对于+,-,*和/,双精度运算是否与浮点运算一样快或更快?
对于64位体系结构,答案是否会改变?
双精度值存储更高的精度,并且是浮点数的两倍,但是Intel CPU是否针对浮点数进行了优化?
也就是说,对于+,-,*和/,双精度运算是否与浮点运算一样快或更快?
对于64位体系结构,答案是否会改变?
Answers:
这里没有一个“英特尔CPU”,特别是在相对于其他对象优化了哪些操作方面!但是,其中大多数(在CPU级别(特别是在FPU内))都可以回答您的问题:
是+,-,*和/的双精度运算与浮点运算一样快还是更快?
是“是”-在CPU内,但除和sqrt的速度double
比慢float
。(假设您的编译器使用SSE2进行标量FP数学运算,就像所有x86-64编译器一样,并且某些32位编译器取决于选项。传统的x87寄存器中的宽度没有不同,仅在内存中(在加载/存储时进行转换) ),因此从历史上看,即使sqrt和除法对于一样慢double
。
例如,Haswell的divsd
吞吐量为每8到14个周期之一(取决于数据),但divss
(标量单)吞吐量为每7个周期之一。x87fdiv
是8到18个循环的吞吐量。(来自https://agner.org/optimize/的数字。延迟与划分的吞吐量相关,但高于吞吐量数字。)
在float
许多库函数的版本一样logf(float)
,并sinf(float)
也将更快比log(double)
及sin(double)
的,因为他们有许多精密更少位得到正确的。他们可以使用较少项的多项式逼近来获得float
vs.double
但是,为每个数字占用两倍的内存显然意味着高速缓存上的负载增加,并且需要更多的内存带宽来填充和溢出那些往返于RAM的高速缓存行;您关心浮点运算的性能的时间就是您要执行许多此类运算时,因此内存和缓存的考虑至关重要。
@Richard的答案指出,还有其他方法可以执行FP操作(SSE / SSE2指令;好的旧MMX仅用于整数),特别适用于对大量数据(“ SIMD”,单指令/多数据)的简单操作),其中每个向量寄存器都可以打包4个单精度浮点数或仅2个双精度浮点数,因此这种效果会更加明显。
最后,您确实必须进行基准测试,但我的预测是,对于合理的基准(即大型;-),您会发现坚持单精度的优势(当然,假设您不需要额外的精度)精确!-)。
在将3.3加20亿次的实验中,结果是:
Summation time in s: 2.82 summed value: 6.71089e+07 // float
Summation time in s: 2.78585 summed value: 6.6e+09 // double
Summation time in s: 2.76812 summed value: 6.6e+09 // long double
因此double更快,并且在C和C ++中是默认设置。它更可移植,并且是所有C和C ++库函数的默认值。Alos double的精度明显高于float。
甚至Stroustrup也建议加倍浮动:
“单精度,双精度和扩展精度的确切含义是由实现定义的。对于选择很重要的问题,选择正确的精度需要对浮点计算有足够的了解。如果您不了解,则请获取建议,花点时间学习,或者加倍努力并希望获得最好的成绩。”
也许唯一应该使用float而不是double的情况是在具有现代gcc的64位硬件上。因为浮子较小;double是8个字节,float是4个字节。
double
在实际上向RTFM推荐时就推荐那里。
另外还可以查看一些基准的真实数据:
For Intel 3770k, GCC 9.3.0 -O2 [3]
Run on (8 X 3503 MHz CPU s)
CPU Caches:
L1 Data 32 KiB (x4)
L1 Instruction 32 KiB (x4)
L2 Unified 256 KiB (x4)
L3 Unified 8192 KiB (x1)
--------------------------------------------------------------------
Benchmark Time CPU Iterations
--------------------------------------------------------------------
BM_FloatCreation 0.281 ns 0.281 ns 1000000000
BM_DoubleCreation 0.284 ns 0.281 ns 1000000000
BM_Vector3FCopy 0.558 ns 0.562 ns 1000000000
BM_Vector3DCopy 5.61 ns 5.62 ns 100000000
BM_Vector3F_CopyDefault 0.560 ns 0.546 ns 1000000000
BM_Vector3D_CopyDefault 5.57 ns 5.56 ns 112178768
BM_Vector3F_Copy123 0.841 ns 0.817 ns 897430145
BM_Vector3D_Copy123 5.59 ns 5.42 ns 112178768
BM_Vector3F_Add 0.841 ns 0.834 ns 897430145
BM_Vector3D_Add 5.59 ns 5.46 ns 100000000
BM_Vector3F_Mul 0.842 ns 0.782 ns 897430145
BM_Vector3D_Mul 5.60 ns 5.56 ns 112178768
BM_Vector3F_Compare 0.840 ns 0.800 ns 897430145
BM_Vector3D_Compare 5.61 ns 5.62 ns 100000000
BM_Vector3F_ARRAY_ADD 3.25 ns 3.29 ns 213673844
BM_Vector3D_ARRAY_ADD 3.13 ns 3.06 ns 224357536
比较3个float(F)或3个double(D)上的操作,并且-BM_Vector3XCopy是复制之前未重复的(1,2,3)初始化向量的纯副本,-BM_Vector3X_CopyDefault,每个副本均重复默认初始化,-BM_Vector3X_Copy123反复初始化(1,2,3),
比较检查两个初始化向量是否相等,
ARRAY_ADD通过std :: valarray汇总vector(1,2,3)+ vector(3,4,5)+ vector(6,7,8)在我的情况下会导致SSE指令。
请记住,这些是孤立的测试,并且结果因编译器设置的不同而有所不同,具体取决于机器,机器或体系结构。对于缓存(问题)和实际用例,这可能是完全不同的。因此,该理论可能与现实大相径庭。找出答案的唯一方法是进行实际测试,例如使用google-benchmark [1]并检查特定问题解决方案的编译器输出结果[2]。
float
适合某种程度的缓存的大小,而double
没有呢?如果您只是在相同级别的缓存中受制于内存带宽,那么大多数情况下,您会期望简单的2倍差异。还是仅以3个值的单个“向量”连续存储,不是以SIMD友好的方式并没有在大数组上摊销的结果中有更多?那么,GCC做了什么样的可怕的汇编,导致复制花了几个周期(3个浮点数,但复制了10倍,3个双倍数)呢?