旧的 DOS C 编译器是否将 double 实现为 32 位?

Did old DOS C compilers implement double as 32-bit?

我正在阅读 Michael Abrash 的图形编程黑皮书,其中全是关于 3D 图形性能的,所以我惊讶地发现那里的很多 C 代码使用 double 而不是 float .我们谈论的是 90 年代初期的计算机(286、386、奔腾)和 MS-DOS C 编译器,那么在那个时代使用 double 的原因是什么? float 不存在或者 doublefloat 的精度与今天不同吗?

简而言之,为什么double用于那个时代的性能关键代码?

据我所知,没有针对 MS-DOS 的 C 编译器使用 32 位宽 double,而是它们都使用 64 位宽 double。 90 年代初期肯定是这种情况。根据对本书 "Floating Point for Real-Time 3D" 章节的快速阅读,Michael Abrash 似乎认为任何精度的浮点数学运算在低于 Pentium CPU 的任何设备上都太慢了。您正在寻找的浮点代码要么用于 Pentium CPUs,要么用于性能无关紧要的非关键路径。对于早期 CPU 的性能关键代码,Abrash 暗示他会改用定点算法。

在很多情况下,使用 float 而不是 double 实际上不会有太大区别。有几个原因。首先,如果您没有安装 x87 FPU (floating-point unit)('486 之前的一个单独的芯片),使用较低的精度不会提高性能足以使软件模拟浮点算法足够快以用于游戏。第二个是大多数 x87 FPU 操作的性能实际上不受精度的影响。在 Pentium 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 占 space 的一半 double。如果您有很多浮点值,那么必须读取和写入一半的内存会对性能产生显着影响。然而,在 Pentium 或更早的 PC 上,这不会导致像今天这样巨大的性能差异。 CPU 速度和 RAM 速度之间的差距在当时并没有那么大,而且浮点性能也相当慢。尽管如此,如果不需要额外的精度(游戏中通常是这种情况),那么优化还是值得的。

请注意,现代 x86 C 编译器通常不使用 x87 FPU 指令进行浮点运算,而是使用标量 SSE 指令,与 x87 指令不同,它有单精度和双精度版本。 (但没有 80 位宽的扩展精度版本。)除了除法,这不会造成任何性能差异,但确实意味着在每次操作后结果总是被截断为 floatdouble 精度.在 x87 FPU 上进行数学运算时,只有在将结果写入内存时才会发生这种截断。这意味着 SSE 浮点代码现在具有可预测的结果,而 x87 FPU 代码具有不可预测的结果,因为通常很难预测编译器何时需要将浮点寄存器溢出到内存中以为其他内容腾出空间。

所以基本上使用 float 而不是 double 不会产生很大的性能差异,除非将浮点值存储在内存中的大数组或其他大型数据结构中。