当大多数情况下建议使用双精度时,为什么浮点数仍是Java语言的一部分?


84

在我所见过的每个地方,它都说在几乎所有方面double都优于float。在Java中float已经过时了double,为什么仍要使用它呢?

我用Libgdx编写了很多程序,它们迫使您使用float(deltaTime等),但是在我看来double,就存储和内存而言,使用起来更容易。

我还读了什么时候使用float以及什么时候使用double,但是如果float真的只对小数点后有很多数字的数字有用,那为什么我们不能仅使用的多种变体之一double

关于人们为什么仍然坚持使用浮点数,即使它实际上不再具有任何优势,是否有任何理由呢?改变这一切只是太多的工作吗?



58
您是如何从该问题的答案中推断出“浮点数实际上仅对小数点后有很多数字的数字有好处”?他们说直接相反
Ordous

20
@Eames注意它说的是“数字”,而不是“数字”。当您需要精度或范围时,浮点会更糟,当您需要大量不那么精确的数据时,浮点会更好。这些答案就是这些。
2016年

29
为什么我们byteshortint当有long
immibis '16

15
一个更合适的问题是“为什么要从具有数十年代码而无缘无故地中断的语言中删除关键字和原始数据类型”?
萨拉2016年

Answers:


169

LibGDX是主要用于游戏开发的框架。

在游戏开发中,您通常必须实时进行大量的数字运算,而您可以获得的任何性能都至关重要。这就是为什么游戏开发人员通常在浮动精度足够好时才使用浮动。

在这种情况下,您不需要考虑CPU中FPU寄存器的大小。实际上,游戏开发中大部分繁琐的工作都是由GPU完成的,并且GPU通常针对浮点数(而非两倍)进行了优化

然后还有:

  • 内存总线带宽(可以在RAM,CPU和GPU之间交换数据的速度)
  • CPU高速缓存(这使得先前的必要性减少)
  • 内存
  • 虚拟RAM

这些都是宝贵的资源,当您使用32位浮点数而不是64位double数时,它们将获得两倍的资源。


2
谢谢!这确实有帮助,因为您深入了解了内存使用情况的变化以及原因
Eames

7
同样,对于SIMD操作,32位值可以具有两倍的吞吐量。正如8bittree的答案所指出的那样,GPU具有更高的性能损失和双精度。
Paul A. Clayton

5
许多图形流水线甚至支持16位半浮点数,以在精度足够的情况下提高性能。
Adi Shavit

22
@phresnel都是。您必须移动位置,更新数据,什么都不要。这是简单的部分。然后,您必须渲染(=读取,旋转,缩放和平移)纹理,距离,并将其转换为屏幕格式...要做的事情很多。
塞布'16

8
@phresnel是游戏开发企业的前运营副总裁,我向您保证,几乎每个游戏都有大量的数字运算。请注意,它通常包含在库中,并且100%从工程师那里抽象出来,我希望他们理解并尊重所有处理工作。魔术反平方根,有人吗?
corsiKa '16

57

浮动使用两倍的内存。

它们的精度可能不到双精度,但是许多应用程序不需要精度。它们的范围比任何类似大小的定点格式大。因此,它们填补了一个利基市场,该利基市场需要广泛的数字范围,但并不需要高精度,并且内存使用率很重要。例如,我过去将它们用于大型神经网络系统。

它们移出Java之外,还广泛用于3D图形中,因为许多GPU都将它们用作其主要格式-在非常昂贵的NVIDIA Tesla / AMD FirePro设备之外,GPU上的双精度浮点运算非常慢。


8
说到神经网络,由于越来越多地使用加速器进行机器学习,CUDA目前支持半精度(16位)浮点变量,精度甚至更低,但内存占用量更低。
JAB

而且,当您对FPGA进行编程时,您倾向于每次都手动选择尾数和指数的位数::v
Sebi,2016年

48

向后兼容

这是使行为保持在现有的语言/库/ ISA / etc中的第一原因。

考虑一下如果他们从Java中删除浮标会发生什么。Libgdx(以及成千上万的其他库和程序)无法正常工作。要更新所有内容,将需要大量的精力,对于许多项目而言,可能要花费数年的时间(只需看看向后兼容性突破的Python 2向Python 3的过渡)。并不是所有的东西都会被更新,某些东西会永远被破坏,因为维护者放弃了它们,也许比他们早了,因为它花费了比他们想要更新更多的精力,或者因为不再可能完成他们的软件应该做的事情去做。

性能

64位双精度占用的内存是32倍浮点运算的两倍,并且处理速度通常总是比32位浮点运算慢(非常罕见的例外是,预期很少使用或根本不使用32位浮点运算的能力,因此没有进行任何优化的努力。除非您正在开发专用硬件,否则在不久的将来您将不会遇到这种情况。)

与您特别相关的Libgdx是一个游戏库。与大多数软件相比,游戏倾向于对性能更加敏感。游戏显卡(例如AMD Radeon和NVIDIA Geforce,而不是FirePro或Quadro)往往具有非常弱的64位浮点性能。由Anandtech提供,以下是在AMDNVIDIA的一些顶级游戏卡上,双精度性能与单精度性能的比较(截至2016年初)

AMD
Card    R9 Fury X      R9 Fury       R9 290X    R9 290
FP64    1/16           1/16          1/8        1/8

NVIDIA
Card    GTX Titan X    GTX 980 Ti    GTX 980    GTX 780 Ti
FP64    1/32           1/32          1/32       1/24

请注意,R9 Fury和GTX 900系列比R9 200和GTX 700系列更新,因此64位浮点的相对性能正在下降。回到足够远的地方,您会发现GTX 580与R9 200系列具有1/8的比率。

如果您有严格的时间限制,而使用较大的倍数并不能带来太大收益,则需要付出1/32的性能代价。


1
请注意,由于32位指令的优化程度不断提高,因此64位浮点的性能相对于32位性能有所下降,而不是因为实际的64位性能正在下降。它还取决于所使用的实际基准;我想知道这些基准测试中突出显示的32位性能缺陷是否是由于内存带宽问题以及实际的计算速度引起的
sig_seg_v 2016年

如果您要谈论图形卡中的DP性能,则绝​​对应该提及Titan / Titan Black。这两个功能模块均允许卡达到1/3的性能,但以单精度性能为代价。
SGR

@sig_seg_v肯定至少在某些情况下64位性能会绝对下降,而不仅仅是相对下降。查看这些结果以获取双精度Folding @ Home基准,其中GTX 780 Ti击败了GTX 1080(另一张1/32比率的显卡)和980 Ti,而在AMD方面,则是7970(1/4比率的显卡)。以及R9 290和R9 290X都击败了R9 Fury系列。将其与基准单精度版本进行比较,在基准版本中,较新的卡都轻而易举地优于其前代产品。
8bittree '16

36

原子操作

除了其他人已经说过的以外,double(和long)特定于Java的缺点是不能保证对64位原始类型的分配是原子的。从Java语言规范,Java SE 8 Edition,第660页(添加了重点):

17.7非原子处理doublelong

出于Java编程语言内存模型的目的,一次对非易失性longdouble值的写入被视为两次单独的写入:一次写入每个32位的一半。这可能导致线程从一次写入中看到64位值的前32位,而从另一次写入中看到后32位的情况。

uck

为避免这种情况,您必须使用关键字声明64位变量volatile,或在分配周围使用其他某种形式的同步


2
您是否不需要以任何方式同步对int和float的并发访问以防止丢失更新并使它们易失以防止过度缓存?我是否认为int / float原子性只能阻止它们永远不会包含不应有的“混合”值,这是我错了吗?
Traubenfuchs

3
@Traubenfuchs也就是说,确实可以保证在那里。我听说过该术语是“撕裂”,我认为它很好地体现了这种效果。Java编程语言模型保证在读取32位值时,该值将在某个时刻写入它们。这是令人惊讶的宝贵保证。
Cort Ammon

关于原子性的这一点非常重要。哇,我忘了这个重要事实。我们可能倾向于认为原始元素本质上是原子的。但在这种情况下不是原子的。
罗勒·布尔克

3

似乎其他答案错过了一个重要点:SIMD体系结构可以处理更少/更多的数据,具体取决于它们是对double还是对float结构进行操作(例如,一次八个浮点值,一次四个双精度值)。

性能注意事项摘要

  • float 在某些CPU(例如某些移动设备)上可能更快。
  • float 使用较少的内存,因此在庞大的数据集中可能会大大减少所需的总内存(硬盘/ RAM)和消耗的带宽。
  • float 与双精度计算相比,单精度计算可能会导致CPU消耗更少的功率(我找不到参考,但至少似乎是合理的)。
  • float 消耗更少的带宽,并且在某些应用中很重要。
  • 通常,SIMD体系结构可以处理两倍于相同数量的数据。
  • float 与两倍的缓存相比,它使用多达一半的缓存。

精度注意事项摘要

  • 在许多应用中float就足够了
  • double 反正有更高的精度

兼容性考虑

  • 如果必须将数据提交给GPU(例如,对于使用OpenGL或其他渲染API 的视频游戏),则浮点格式的速度要快得多double(这是因为GPU制造商试图增加图形核的数量,并且因此,他们尝试在每个内核中节省尽可能多的电路,因此进行优化float以创建内部包含更多内核的GPU)
  • 旧的GPU和某些移动设备无法接受double作为内部格式(用于3D渲染操作)

一般提示

  • 在现代台式机处理器(可能还有大量的移动处理器)上,您基本上可以假定double在堆栈上使用临时变量可免费提供额外的精度(超精度而不会影响性能)。
  • 永远不要使用超出您需要的精度(您可能不知道您真正需要多少精度)。
  • 有时,您只是受到值范围的强迫(如果使用float,某些值将是无限的,如果使用,则可能是有限的值double
  • 仅使用float或仅使用double极大地帮助编译器对指令进行SIMD修改。

请参阅以下PeterCordes的评论以获取更多见解。


1
double只有在使用x87 FPU的x86上,temporaries才是免费的,而对于SSE2不可用。使用double临时程序自动向量化循环意味着将拆包floatdouble,这需要额外的指令,并且每个向量处理一半的元素。如果没有自动向量化,转换通常可以在加载或存储期间即时进行,但是当您在表达式中混合使用浮点数和双精度数时,这意味着额外的指令。
彼得·科德斯

1
在现代x86 CPU上,div和sqrt的float速度快于double速度,但其他速度相同(当然不包括SIMD向量宽度问题或内存带宽/缓存占用空间)。
彼得·科德斯

@PeterCordes感谢您提出的观点。我不知道div和sqrt的差异
GameDeveloper

0

除了提到的其他原因:

如果您具有测量数据,无论是压力,流量,电流,电压还是其他任何数据,通常都可以通过具有ADC的硬件来完成。

ADC通常具有10或12位,很少有14或16位。但是,让我们坚持使用16位1-如果要满量程测量,则精度为1/65535。这意味着从65534/65535更改为65535/65535只是这一步-1/65535。大约是1.5E-05。浮子的精度约为1E-07,因此要好得多。这意味着您不会因为使用float这些数据而丢失任何东西。

如果对浮子进行过多的计算,则doubles在精度方面的性能会稍差一些,但通常并不需要该精度,因为您通常不关心测量的电压是2 V还是2.00002V。 ,如果将此电压转换为压力,则无需担心是3 bar还是3.00003 bar。

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.