定点数学比浮点数快吗?

Is fixed point math faster than floating point?

多年前,在 1990 年代初期,我构建了图形包,这些图形包基于定点算法和 cos、sin 的预计算表以及使用牛顿近似法的 sqrt 和对数近似的缩放方程优化了计算。这些先进的技术似乎已经成为图形和内置数学处理器的一部分。大约 5 年前,我进行了数值分析 class,其中涉及一些旧技术。我已经编码了近 30 年,很少看到使用那些旧的定点优化,即使在为世界 class 粒子加速器实验开发 GPGPU 应用程序之后也是如此。定点方法在整个软件行业的任何地方是否仍然有用,或者这些知识的用处现在已经永远消失了吗?

不动点在不支持任何小数类型的平台上用处不大;例如,我为 PIC16F 系列微控制器实现了一个 24 位定点类型(稍后会详细介绍我为什么选择定点)。

然而,几乎每个现代 CPU 都支持微码或硬件级别的浮点,因此对定点的需求不大。

定点数在它们可以表示的范围内受到限制 - 考虑 64 位 (32.32) 定点数与 64 位浮点数:64 位定点数的十进制分辨率为 1/ (232),而浮点数的十进制分辨率为up to 1/(253);定点数可以表示高达 231 的值,而浮点数可以表示 22 23。如果您需要更多,大多数现代 CPU 都支持 80 位浮点值。

当然,浮点数最大的缺点是在极端情况下精度有限——例如在定点中,它需要更少的位来表示 9000000000000000000000000000000.000000000000000000000000000000002。当然,使用浮点数,您可以获得更好的十进制算术平均使用精度,我还没有看到一个应用程序,其中十进制算术像上面的例子一样极端,但也不会溢出等效的定点大小。

我为 PIC16F 实现定点库而不是使用现有浮点库的原因是代码大小,而不是速度:16F88 有 384 字节的可用 RAM 和 4095 条指令的空间 总计。为了添加两个预定义宽度的定点数,我在我的代码中内联了带进位的整数加法(定点无论如何都不会移动);为了将两个定点数相乘,我使用了一个带有扩展 32 位定点的简单移位加法函数,尽管这不是最快的乘法方法,但为了节省更多代码。

因此,当我只需要一两个基本算术运算时,我可以添加它们而无需用完所有程序存储空间。相比之下,该平台上免费提供的浮点库约占设备总存储量的 60%。相比之下,软件浮点库大多只是一些算术运算的包装器,根据我的经验,它们大多是全有或全无,因此将代码大小减半因为你只需要一半的功能是行不通的太好了

不动点通常不会在速度上提供太多优势,因为它的表示范围有限:您需要多少位来表示 1.7E+/-308,精度为 15 位,与64位双?如果我的计算是正确的,你需要大约 2020 位。我敢打赌它的表现不会那么好。

三十年前,当硬件浮点数相对较少时,非常特殊用途的定点(甚至缩放整数)算法可以比基于软件的浮点数提供显着的性能提升,但前提是允许值的范围可以用缩放整数算法有效地表示(当没有可用的协处理器时,最初的 Doom 使用这种方法,例如在 1992 年的我的 486sx-25 上 - 在超频的超线程 Core i7 运行 上输入这个 4.0 GHz 的 GeForce 卡有超过 1000 个独立的浮点计算单元,这似乎有点不对劲,虽然我不确定是 486 还是 i7...)。

浮点数由于它可以表示的值的范围而更通用,并且在 CPUs 和 GPU 上以硬件实现,它在各个方面都胜过定点数,除非你真的需要以巨大的定点大小和非常慢的代码为代价的超过 80 位浮点精度。

嗯,我编码了 2 年,我的经验是使用定点有 3 个主要原因:

  1. 没有可用的 FPU

    定点对DSP、MCU、FPGA和一般芯片设计仍然有效。此外,没有定点核心单元,浮点单元也无法工作,因此所有 bigdecimal 库都必须使用定点...显卡也大量使用定点(标准化设备坐标)。

  2. FPU精度不够

    如果你进行天文计算,你很快就会遇到极端情况并需要处理它们。例如,简单的 Newtonian/D'Alembert 集成或大气光线追踪在大尺度和低粒度上非常快速地击中了精度障碍。我通常使用浮点双精度数组来解决这个问题。对于已知 input/output 范围的情况,不动点通常是更好的选择。查看一些达到 FPU 障碍的示例:

    • ray and ellipsoid intersection accuracy improvement
  3. 速度

    过去 FPU 真的很慢(尤其是在 x86 架构上)由于接口和 api 它使用。每个 FPU 指令都会产生一个中断,更不用说操作数和结果传输过程了... CPU ALU[= 中的移位操作很少52=] 通常更快。

    如今情况不再如此,ALUFPU 速度相当。例如这里我对 CPU/FPU 操作的测量(在小型 Win32 C++ 应用程序中):

      fcpu(0) = 3.194877 GHz // tested on first core of AMD-A8-5500 APU 3.2GHz Win7 x64 bit
    
      CPU 32bit integer aritmetics:
      add = 387.465 MIPS
      sub = 376.333 MIPS
      mul = 386.926 MIPS
      div = 245.571 MIPS
      mod = 243.869 MIPS
    
      FPU 32bit float aritmetics:
      add = 377.332 MFLOPS
      sub = 385.444 MFLOPS
      mul = 383.854 MFLOPS
      div = 367.520 MFLOPS
    
      FPU 64bit double aritmetics:
      add = 385.038 MFLOPS
      sub = 261.488 MFLOPS
      mul = 353.601 MFLOPS
      div = 309.282 MFLOPS
    

    值随时间变化,但数据类型之间的比较几乎相同。就在几年前,由于 2 倍大的数据传输,双倍速度变慢。但在其他平台上,速度差异可能仍然有效。