【问题标题】:Is using double faster than float?使用 double 比 float 快吗?
【发布时间】:2025-12-25 09:35:12
【问题描述】:

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

也就是说,双精度运算与 +、-、* 和 / 的浮点运算一样快还是更快?

64 位架构的答案会改变吗?

【问题讨论】:

  • 这取决于你用它们做什么。理论上,内存带宽可能会进入其中。你有更多信息吗?
  • 仅供参考一个重复的问题here has some good information also

标签: c++ performance x86 intel osx-snow-leopard


【解决方案1】:

Alex Martelli 的回答已经足够好了,但我想提一个错误但有些流行的测试方法,它可能误导了一些人:

#include <cstdio>
#include <ctime>
int main() {
  const auto start_clock = clock();
  float a = 0;
  for (int i = 0; i < 256000000; i++) {
    // bad latency benchmark that includes as much division as other operations
    a += 0.11;  // note the implicit conversions of a to double to match 0.11
    a -= 0.13;  // rather than 0.11f
    a *= 0.17;
    a /= 0.19;
  }
  printf("c++ float duration = %.3f\n", 
    (double)(clock() - start_clock) / CLOCKS_PER_SEC);
  printf("%.3f\n", a);
  return 0;
}

错了! C++ 默认使用double,如果将+= 0.11 替换为+= 0.11f,在x86 CPU 上,float 通常会比双倍快。

顺便说一下,在现代 SSE 指令集上,float 和 double 在 CPU 内核本身中具有相同的速度 except of division operationfloat 如果您有数组,则较小的缓存未命中可能会更少。

如果编译器可以自动向量化,浮点向量每条指令处理的元素数量是双精度的两倍。

【讨论】:

    【解决方案2】:

    另外一些真实的数据可以一睹为快:

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

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

    • ARRAY_ADD 通过 std::valarray 对向量(1,2,3)+向量(3,4,5)+向量(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

    【讨论】:

    • 您是否选择了使float 适合某种级别的缓存而double 不适合的大小?如果您只是在同一级别的缓存中受到内存带宽的限制,那么您会期望大多数情况下的简单因素为 2。或者,对于单个“向量”的 3 个值是否有更多的结果是连续存储的,不是以 SIMD 友好的方式,也不是在一个大数组上摊销?那么 GCC 做了什么样的可怕的 asm 导致复制需要几个周期来 3 个浮点数但 10 倍于 3 个双精度数?
    • 这是一个很好的观察,彼得。这里所有的理论解释都是有效的,值得了解。我的结果是可能的许多不同解决方案的一种设置的特例。我的观点不是我的解决方案可能有多糟糕,而是在实践中存在太多未知数,您必须测试您的特定用例才能确定。我很欣赏你的分析。这对我有帮助:) 但是让我们专注于 OP 提出的问题。
    • 好吧,这很公平,演示当你将 float 更改为 double 时编译器会无缘无故地完全糟糕的事实很有趣。您也许应该指出,这就是您的答案所显示的,而不是任何基本问题或一般情况。
    • 这里的罪魁祸首当然是我。随着我对“易失性”的恶魔般的使用。编译器没有机会优化任何东西,这也是我对这种特殊情况的目标。所以不要判断 GCC 太难:)
    • 补充一些背景故事:我和 OP 一样好奇。使用 double 而不是 float 会有所不同吗?我如何阅读结果:第一个是孤立的,只有最后两个表明在现实世界的情况下会发生什么 -> 没有区别。在我的特殊情况下。多亏了 Corona,我才有时间走进这个兔子洞。这种调查可能会增加很多时间,您必须自行决定是否可行。假设 FPS 从 999 提高到 1177...
    【解决方案3】:

    要考虑的另一点是您是否使用 GPU(显卡)。我在一个数字密集型项目中工作,但我们不需要双重提供的精确度。我们使用 GPU 卡来帮助进一步加快处理速度。 CUDA GPU 需要一个特殊的包来支持双倍,GPU 上的本地 RAM 量非常快,但非常稀缺。因此,使用浮点数还会使我们可以在 GPU 上存储的数据量翻倍。

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

    所以对于我正在使用的应用程序来说,差异非常重要。

    【讨论】:

      【解决方案4】:

      没有一个单一的“英特尔 CPU”,尤其是在哪些操作相对于其他人进行了优化方面!但其中大多数在 CPU 级别(特别是在 FPU 内)是这样的问题:

      是双重操作同样快或 比 +, -, 的浮点运算更快 * 和 /?

      是“是”——在 CPU 中,除法和 sqrt 除外,它们是 somewhat slower for double than for float。 (假设您的编译器使用 SSE2 进行标量 FP 数学,就像所有 x86-64 编译器一样,以及一些 32 位编译器取决于选项。旧版 x87 在寄存器中没有不同的宽度,只有在内存中(它在加载/存储时转换),所以从历史上看,double 的 sqrt 和除法也一样慢。

      例如,Haswell 的 divsd 吞吐量为每 8 到 14 个周期 1 个(取决于数据),但 divss(标量单)吞吐量为每 7 个周期 1 个。 x87 fdiv 是 8 到 18 个周期的吞吐量。 (来自https://agner.org/optimize/ 的数字。延迟与除法的吞吐量相关,但高于吞吐量数字。)

      float 版本的许多库函数(如 logf(float)sinf(float))也将比 log(double)sin(double) 更快,因为它们获得的精度要少得多正确的。他们可以使用较少项的多项式近似来获得floatdouble 的完全精度


      然而,每个数字占用两倍的内存显然意味着缓存负载更重,内存带宽更大来填充和溢出这些缓存行/到内存;您关心浮点运算性能的时间是在您执行大量此类运算时,因此内存和缓存考虑因素至关重要。

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

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

      【讨论】:

      • 这也取决于缓存块的大小,对吗?如果您的缓存检索 64 位或更大的块,那么 double 将与 float 一样有效(如果不是更快的话),至少就内存读取/写入而言。
      • @Razor 如果您使用的浮点数与缓存行中的浮点数完全相同,那么如果您使用双精度数,CPU 将不得不获取两个缓存行。但是,在阅读 Alex 的答案时,我想到的缓存效果是:您的浮点数集适合您的第 n 级缓存,但相应的双精度集不适合。在这种情况下,如果您使用浮点数,您将体验到性能的巨大提升。
      • @Peter,是的,这是有道理的,假设你有一个 32 位缓存线,使用双精度值每次都必须获取两次。
      • @Razor,问题不在于仅获取/存储 one 值 - 正如@Peter 的焦点正确表明的那样,您通常会获取“几个”要操作的值(数字数组将是一个典型示例,对此类数组项的操作在数值应用中非常常见)。有反例(例如,一个指针连接的树,其中每个节点只有一个数字和很多其他东西:那么这个数字是 4 或 8 个字节将无关紧要),这也是我在最后你必须进行基准测试,但这个想法通常适用。
      • @Alex Martelli,我明白了。这是有道理的。
      【解决方案5】:

      唯一真正有用的答案是:只有你自己知道。您需要对场景进行基准测试。指令和内存模式的微小变化可能会产生重大影响。

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

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

      【讨论】:

        【解决方案6】:

        我只是想补充一下__m256? 系列相同指令多数据 (SIMD) C++ 内在函数在任何一个 4 上运行的已有很好的答案 double 并行(例如 _mm256_add_pd),或 8 floats 并行(例如 _mm256_add_ps)。

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

        【讨论】:

          【解决方案7】:

          在2000000000次加3.3的实验中,结果是:

          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 都推荐 double over float:

          “单精度,双精度和扩展精度的确切含义是实现定义的。为选择很重要的问题选择正确的精度需要对浮点计算有深刻的理解。如果你没有那个理解,获得建议,花时间学习,或使用双倍并希望最好。”

          也许您应该使用 float 而不是 double 的唯一情况是在具有现代 gcc 的 64 位硬件上。因为浮动更小; double 是 8 个字节,float 是 4 个字节。

          【讨论】:

          • +1 表示努力进行一些计时。但是 Stroustrup 不推荐使用 'double',因为它更快,但是因为额外的精度。关于您的最后一条评论,如果您需要额外的精度而不是节省内存,那么您很可能希望在 32 位硬件上使用“双精度”。这就引出了一个问题:即使在 32 位硬件上使用现代 FPU 进行 64 位计算,double 是否也比 float 快?
          • 百分之几秒的差异感觉仍然在实验误差范围内。特别是如果还有其他东西(比如可能是未展开的循环......)。
          • 当 Stroustrup 实际向 RTFM 推荐时,说 Stroustrup 在那里推荐 double 有点牵强。
          • 什么硬件,什么编译器+选项,什么代码?如果您在同一个程序中对所有 3 个都进行了计时,则时钟速度加速时间解释了第一个较慢。显然,您没有启用自动矢量化(如果没有 -ffast-math 或其他任何东西,就不可能进行缩减,因为 FP 数学不是严格关联的)。因此,这仅证明当瓶颈是标量 FP 添加延迟时没有速度差异。关于 64 位硬件的位也没有任何意义:在任何普通硬件上,float 始终是 double 大小的一半。 64 位硬件的唯一区别是 x86-64 以 SSE2 作为基线。
          【解决方案8】:

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

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

          【讨论】:

            【解决方案9】:

            浮点通常是对通用 CPU 的扩展。因此,速度将取决于所使用的硬件平台。如果平台支持浮点,如果有什么区别我会很惊讶。

            【讨论】: