我正在阅读Michael Abrash的图形编程黑皮书,其中都是关于3D图形性能的,所以我惊讶地发现那里的许多C代码使用double
而不是float
。我们谈论的是90年代早期的计算机(286,386,奔腾)和MS-DOS C编译器,那么在那个时代使用double
的原因是什么?float
不存在还是double
和float
的精度与今天不同?
简而言之,为什么double
在那个时代用于性能关键型代码?
据我所知,没有针对MS-DOS的C编译器使用32位宽double
,而是都使用64位宽double
。 到90年代初,情况确实如此。根据对本书"实时3D浮点"一章的快速阅读,迈克尔·阿布拉什(Michael Abrash)似乎认为,任何精度的浮点数学在低于奔腾CPU的任何东西上都太慢了。 您正在寻找的浮点代码要么是用于奔腾 CPU,要么是在性能无关紧要的非关键路径上使用的。 对于早期CPU的性能关键型代码,Abrash暗示他会使用定点算法。
在很多情况下,使用float
而不是double
实际上并没有太大区别。有几个原因。 首先,如果您没有安装 x87 FPU(浮点单元)(486 之前的单独芯片),使用较低的精度不足以提高性能,无法使软件模拟浮点运算足够快以用于游戏。 其次,大多数 x87 FPU 操作的性能实际上并未受到精度的影响。 在奔腾 CPU 上,如果以较窄的精度执行,则只有除法速度更快。对于早期的 x87 FPU,我不确定精度会影响除法,尽管它可能会影响 80387 的乘法性能。在所有 x87 FPU 上,无论精度如何,添加的速度都是相同的。
第三,所使用的特定 C 数据类型,无论是 32 位float
、64 位double
,甚至是许多编译器支持的 80 位long double
,实际上都不会影响 FPU 在计算过程中使用的精度。 这是因为 FPU 对于它支持的三种不同精度没有不同的指令(或编码)。没有办法告诉它执行float
加法或double
除法。 相反,它以FPU控制寄存器中设置的给定精度执行所有算术。(或者更准确地说,它执行算术,就像使用无限精度一样,然后将结果四舍五入到设定的精度。虽然每次使用浮点指令时都可以更改此寄存器,但这会导致性能大幅下降,因此编译器从未这样做过。 相反,他们只是在程序启动时将其设置为 80 位或 64 位精度,然后保持原样。
现在,将 FPU 设置为单精度实际上是 3D 游戏的常用技术。 这意味着浮点运算,无论是使用double
还是float
类型,都将使用单精度算术来执行。虽然这最终只会影响浮点除法的性能,但 3D 图形编程往往会在关键代码中执行大量除法(例如透视除法),因此这可能会显着提高性能。
但是,有一种方法可以使用float
而不是double
来提高性能,这仅仅是因为float
占用了double
一半的空间。 如果有很多浮点值,则必须读取和写入一半的内存可能会对性能产生重大影响。 但是,在奔腾或更早的PC上,这不会导致今天的巨大性能差异。 当时 CPU 速度和 RAM 速度之间的差距并不大,浮点性能也慢了一点。尽管如此,如果不需要额外的精度,那么优化将是值得的,就像游戏中通常的情况一样。
请注意,现代 x86 C 编译器通常不使用 x87 FPU 指令进行浮点运算,而是使用标量 SSE 指令,与 x87 指令不同,这些指令确实有单精度和双精度版本。(但没有 80 位宽扩展精度版本。 除法外,这不会对性能产生任何影响,但确实意味着每次操作后结果始终被截断为float
或double
精度。 在 x87 FPU 上进行数学运算时,此截断仅在结果写入内存时发生。 这意味着 SSE 浮点代码现在具有可预测的结果,而 x87 FPU 代码具有不可预测的结果,因为通常很难预测编译器何时需要将浮点寄存器溢出到内存中以便为其他内容腾出空间。
因此,基本上使用float
而不是double
不会对性能产生很大影响,除非将浮点值存储在内存中的大数组或其他大型数据结构中。