使用浮点数快两倍吗?


71

双精度值存储更高的精度,并且是浮点数的两倍,但是Intel CPU是否针对浮点数进行了优化?

也就是说,对于+,-,*和/,双精度运算是否与浮点运算一样快或更快?

对于64位体系结构,答案是否会改变?


这取决于您对他们的处理方式。从理论上讲,内存带宽可以纳入其中。您还有更多信息吗?
基兰·约翰斯通

2
仅供参考,这里有一个重复的问题也有一些很好的信息
Trevor Boyd Smith,

Answers:


79

这里没有一个“英特尔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)的,因为他们有许多精密更少位得到正确的。他们可以使用较少项的多项式逼近来获得floatvs.double


但是,为每个数字占用两倍的内存显然意味着高速缓存上的负载增加,并且需要更多的内存带宽来填充和溢出那些往返于RAM的高速缓存行;您关心浮点运算的性能的时间就是您要执行许多此类运算时,因此内存和缓存的考虑至关重要。

@Richard的答案指出,还有其他方法可以执行FP操作(SSE / SSE2指令;好的旧MMX仅用于整数),特别适用于对大量数据(“ SIMD”,单指令/多数据)的简单操作),其中每个向量寄存器都可以打包4个单精度浮点数或仅2个双精度浮点数,因此这种效果会更加明显。

最后,您确实必须进行基准测试,但我的预测是,对于合理的基准(即大型;-),您会发现坚持单精度的优势(当然,假设您不需要额外的精度)精确!-)。


1
这还取决于缓存块的大小,对吗?如果您的缓存检索到64位或更大的块,那么就内存读/写而言,双精度与浮点数一样有效(如果不是更快的话)。
剃刀风暴

5
@Razor如果在缓存行中使用的浮点数完全相同,那么如果您使用double,则CPU将必须获取两条缓存行。但是,当我阅读Alex的答案时,我想到的缓存效果是:您的float集合适合您的n级缓存,但对应的double集合却不适合。在这种情况下,如果使用浮点数,则性能会大大提高。
Peter G.

@Peter,是的,说您有一个32位的缓存行,使用double每次必须获取两次。
剃刀风暴

1
@Razor,问题实际上并不是仅获取/存储一个值-而是,正如@Peter的焦点正确指出的那样,您经常要获取要操作的“几个”值(一个数字数组就是一个典型的例子,以及在数值应用中非常常见的此类数组项目的操作)。有一些反例(例如,一个指针连接的树,其中每个节点只有一个数字,还有很多其他东西:那么将该数字设为4或8个字节就无关紧要了),这就是为什么我在最终您必须进行基准测试,但是这个想法通常适用。
Alex Martelli

@Alex Martelli,我明白了。这就说得通了。
剃刀风暴

27

如果所有浮点计算都在FPU内执行,则不会,在double计算和float计算之间没有区别,因为浮点运算实际上是在FPU堆栈中以80位精度执行的。FPU堆栈的条目会适当舍入,以将80位浮点格式转换为doublefloat浮点格式。sizeof(double)RAM与sizeof(float)字节之间来回移动与字节移动是唯一的速度差异。

但是,如果您具有可矢量化的计算,则可以使用SSE扩展float与两个double计算同时运行四个计算。因此,巧妙地使用SSE指令和XMM寄存器可以提高仅使用floats的计算的吞吐量。


13

要考虑的另一点是,如果您使用的是GPU(图形卡)。我正在处理一个数字密集型项目,但是我们并不需要双重精度。我们使用GPU卡来帮助进一步加快处理速度。CUDA GPU需要一个特殊的程序包来支持双倍,并且GPU上的本地RAM数量非常快,但非常稀缺。结果,使用float会使我们可以存储在GPU上的数据量增加一倍。

还有一点是记忆。浮点数占用的内存是两倍的内存的一半。如果要处理非常大的数据集,这可能是一个非常重要的因素。如果使用double意味着您必须缓存到磁盘而不是纯RAM,那么您的差异将会很大。

因此,对于我正在使用的应用程序,区别非常重要。


11

我只想补充到现有的伟大的答案是,__m256?家庭同指令多数据(SIMD)C ++内部函数进行操作或者 4个 doubleS IN并行(例如_mm256_add_pd),或8个 floatS IN并行(例如_mm256_add_ps)。

我不确定这是否可以转化为实际的速度提高,但是当使用SIMD时,似乎每条指令可以处理2倍的浮点数。


10

在将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个字节。


3
+1是为了努力做一些计时。但是Stroustrup不建议使用“双精度”,因为它更快,但是因为精度更高。关于最后一条评论,如果您需要的不仅仅是保存内存,还需要更高的精度,那么很有可能您希望在32位硬件上使用“ double”。这就引出了一个问题:即使在具有执行64位计算的现代FPU的32位硬件上,浮点运算速度是否也比浮点运算快一倍?
布伦特·浮士德

1
几分之一秒的差异似乎仍在实验误差范围之内。尤其是如果还有其他东西(例如未展开的循环……)。
imallett

4
可以说Stroustrupdouble在实际上向RTFM推荐时就推荐那里。
sunside

1
什么硬件,什么编译器+选项,什么代码?如果您在同一程序中对所有3个时钟进行计时,则时钟速度加速时间将解释第一个速度较慢。显然,您没有启用自动矢量化功能(如果没有-ffast-math或其他功能,则不可能进行归约,因为FP数学不是严格关联的)。因此,这仅证明瓶颈是标量FP添加延迟时没有速度差异。关于64位硬件的说法也没有道理:在任何普通硬件上,float总是两倍大小的一半。64位硬件上的唯一区别是x86-64以SSE2为基准。
彼得·科德斯

8

唯一真正有用的答案是:只有您能说出来。您需要对方案进行基准测试。指令和存储模式的微小变化可能会产生重大影响。

如果您使用的是FPU或SSE类型的硬件,那肯定会很重要(以前,它以80位的扩展精度完成所有工作,因此double会更接近;后来是32位,即float)。

更新:s / MMX / SSE /,如另一个答案中所述。


2

浮点数通常是对通用CPU的扩展。因此,速度将取决于所使用的硬件平台。如果平台具有浮点支持,那么如果有任何区别,我将感到惊讶。


-1

另外还可以查看一些基准的真实数据:

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),

  • 加/乘每个初始化3个向量(1,2,3),并将第一个和第二个相加/相乘到第三个,
  • 比较检查两个初始化向量是否相等,

  • ARRAY_ADD通过std :: valarray汇总vector(1,2,3)+ vector(3,4,5)+ vector(6,7,8)在我的情况下会导致SSE指令。

请记住,这些是孤立的测试,并且结果因编译器设置的不同而有所不同,具体取决于机器,机器或体系结构。对于缓存(问题)和实际用例,这可能是完全不同的。因此,该理论可能与现实大相径庭。找出答案的唯一方法是进行实际测试,例如使用google-benchmark [1]并检查特定问题解决方案的编译器输出结果[2]。

  1. https://github.com/google/benchmark
  2. https://sourceware.org/binutils/docs/binutils/objdump.html- > objdump -S
  3. https://github.com/Jedzia/oglTemplate/blob/dd812b72d846ae888238d6f726d503485b796b68/benchmark/Playground/BM_FloatingPoint.cpp

1
您是否选择了float适合某种程度的缓存的大小,而double没有呢?如果您只是在相同级别的缓存中受制于内存带宽,那么大多数情况下,您会期望简单的2倍差异。还是仅以3个值的单个“向量”连续存储,不是以SIMD友好的方式并没有在大数组上摊销的结果中有更多?那么,GCC做了什么样的可怕的汇编,导致复制花了几个周期(3个浮点数,但复制了10倍,3个双倍数)呢?
彼得·科德斯

彼得,这是一个很好的观察。这里的所有理论解释都是有效的,并且很容易理解。我的结果是一种特殊情况,即可能设置许多不同解决方案。我的观点不是我的解决方案有多可怕,而是在实践中有太多的未知数,您必须测试特定的用例才能确定。感谢您的分析。这对我有帮助:)但是让我们集中讨论OP提出的问题。
杰兹亚

好的,这很公平,当您将float更改为double时,演示编译器完全无故吸吮这一事实很有趣。您可能应该指出,这就是您的答案所显示的内容,而不是任何基本问题或一般情况。
Peter Cordes

当然,这里有罪的是我。与我恶魔般地使用“挥发性”。编译器没有机会进行任何优化,这也是我针对这种特殊情况的目标。因此,请不要对GCC进行艰难的判断:)
Jedzia

添加一些背景故事:我和OP一样好奇。使用double代替float会有所不同吗?我如何看待结果:前一个是孤立的,只有后两个表明了在现实世界中的预期结果->没有区别。在我的特殊情况下。多亏了Corona,我才有时间钻进这个兔子洞。这种调查可能会花费很多时间,您必须自己决定是否可行。假设FPS从999提升到1177 ...
吉兹亚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.