【问题标题】:Floating point vs integer calculations on modern hardware现代硬件上的浮点与整数计算
【发布时间】:2011-02-02 18:20:41
【问题描述】:

我在 C++ 中做一些对性能至关重要的工作,我们目前正在使用整数计算来解决本质上是浮点的问题,因为“它更快”。这会导致很多烦人的问题,并添加很多烦人的代码。

现在,我记得在大约 386 天时读到过浮点计算如此缓慢的情况,我相信 (IIRC) 有一个可选的协处理器。但是现在,随着 CPU 的复杂性和强大程度呈指数级增长,如果进行浮点或整数计算,“速度”肯定没有区别吗?尤其是因为与导致管道停顿或从主内存中获取某些内容相比,实际计算时间很短?

我知道正确的答案是在目标硬件上进行基准测试,什么是测试这个的好方法?我编写了两个小型 C++ 程序,并将它们的运行时间与 Linux 上的“时间”进行了比较,但实际运行时间变化太大(无助于我在虚拟服务器上运行)。除了花费我一整天的时间来运行数百个基准测试、制作图表等,我能做些什么来对相对速度进行合理的测试吗?有什么想法或想法吗?我完全错了吗?

我使用的程序如下,它们无论如何都不相同:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{
    int accum = 0;

    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += rand( ) % 365;
    }
    std::cout << accum << std::endl;

    return 0;
}

方案二:

#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>

int main( int argc, char** argv )
{

    float accum = 0;
    srand( time( NULL ) );

    for( unsigned int i = 0; i < 100000000; ++i )
    {
        accum += (float)( rand( ) % 365 );
    }
    std::cout << accum << std::endl;

    return 0;
}

提前致谢!

编辑:我关心的平台是在桌面 Linux 和 Windows 机器上运行的常规 x86 或 x86-64。

编辑 2(从下面的评论中粘贴):我们目前拥有广泛的代码库。真的,我已经反对我们“不能使用浮点数,因为整数计算更快”的概括 - 我正在寻找一种方法(如果这甚至是真的)来反驳这个概括的假设。我意识到,如果不做所有工作并事后分析,我们就无法预测确切的结果。

无论如何,感谢您所有出色的回答和帮助。随意添加任何其他内容:)。

【问题讨论】:

  • 你现在的测试是微不足道的。程序集也可能几乎没有区别(例如,addl 替换为 fadd)。真正获得良好测量的唯一方法是获取真实程序的核心部分并分析其不同版本。不幸的是,如果不付出大量的努力,这可能会非常困难。也许告诉我们目标硬件和你的编译器会帮助人们至少给你预先存在的经验等。关于你的整数使用,我怀疑你可以制作一种fixed_point 模板类,这将极大地简化此类工作。跨度>
  • 仍有很多架构没有专用的浮点硬件 - 一些解释您关心的系统的标签将帮助您获得更好的答案。
  • 我相信我的 HTC Hero (android) 中的硬件没有 FPU,但 Google NexusOne (android) 中的硬件有。你的目标是什么?台式机/服务器电脑?上网本(可能是arm+linux)?手机?
  • 如果您想在 x86 上实现快速 FP,请尝试使用优化和 SSE 代码生成进行编译。 SSE(任何版本)至少可以在一个周期内进行浮点加法、减法和乘法运算。 Divide、mod 和更高级别的函数总是很慢。另请注意,float 会获得速度提升,但通常double 不会。
  • 定点整数通过使用多个整数运算来近似 FP,以防止结果溢出。这几乎总是比仅使用现代台式机 CPU 中功能强大的 FPU 慢。例如定点 mp3 解码器 MAD 比 libmpg123 慢,尽管它对于定点解码器来说质量很好,但 libmpg123 的舍入误差仍然较小。 wezm.net/technical/2008/04/mp3-decoder-libraries-compared 用于 PPC G5 的基准测试。

标签: c++ x86 floating-point x86-64


【解决方案1】:

除非您正在编写每秒调用数百万次的代码(例如,在图形应用程序中在屏幕上绘制一条线),否则整数与浮点运算很少成为瓶颈。

效率问题的第一步通常是分析您的代码,以查看运行时的实际花费。用于此的 linux 命令是 gprof

编辑:

虽然我想你总是可以使用整数和浮点数来实现画线算法,但调用它很多次,看看它是否会有所不同:

http://en.wikipedia.org/wiki/Bresenham's_algorithm

【讨论】:

  • 科学应用使用 FP。 FP 的唯一优点是精度是尺度不变的。这就像科学记数法。如果您已经知道数字的比例(例如,线长是像素数),则可以避免使用 FP。但在你开始画线之前,这不是真的。
【解决方案2】:

加法比rand 快得多,所以你的程序(尤其是)没用。

您需要识别性能热点并逐步修改您的程序。听起来您的开发环境存在需要首先解决的问题。对于一个小问题集是否不可能在您的 PC 上运行您的程序?

一般来说,尝试使用整数运算的 FP 作业会导致速度变慢。

【讨论】:

  • 是的,以及浮点版本中从 rand 整数到浮点数的转换。关于更好的测试方法有什么想法吗?
  • 如果您想分析速度,请查看 POSIX 的 timespec_t 或类似的东西。记录循环开始和结束的时间并取差值。然后将rand 数据生成移出循环。确保您的算法从数组中获取所有数据并将其所有数据放入数组中。这会自行获取您的实际算法,并从您的分析循环中获取设置、malloc、结果打印、除任务切换和中断之外的所有内容。
  • @maxpenguin:问题是你在测试什么。 Artem 假设您正在做图形,Carl 考虑过您是否在没有 FP 的嵌入式平台上,我假设您正在为服务器编写科学代码。您不能概括或“编写”基准。基准是从您的程序所做的实际工作中抽取的。我可以告诉你的一件事是,如果你触及程序中对性能至关重要的元素,无论它是什么,它都不会保持“基本相同的速度”。
  • 好点和好答案。我们目前拥有广泛的代码库。真的,我已经反对我们“不能使用浮点数,因为整数计算更快”的概括 - 我正在寻找一种方法(如果这甚至是真的)来反驳这个概括的假设。我意识到,如果不做所有工作并事后分析,就不可能预测我们的确切结果。无论如何,感谢您的帮助。
【解决方案3】:

基于那个非常可靠的“我听说过的事情”,在过去,整数计算的速度大约是浮点数的 20 到 50 倍,而现在它的速度还不到两倍。

【讨论】:

  • 请考虑再看一遍,提供的不仅仅是意见(尤其是考虑到意见似乎与收集的事实相悖)
  • @MrMesees 虽然这个答案不是非常有用,但我想说它与您所做的测试一致。历史琐事可能也很好。
  • 作为一个曾经和286一起工作过的人,我可以确认; “是的......他们是!”
【解决方案4】:

唉,我只能给你一个“视情况而定”的答案......

根据我的经验,性能有很多很多变量……尤其是在整数和浮点数学之间。它因处理器而异(即使在同一个系列中,例如 x86),因为不同的处理器具有不同的“管道”长度。此外,一些运算通常非常简单(例如加法)并且通过处理器具有加速路径,而其他运算(例如除法)则需要更长的时间。

另一个大变量是数据所在的位置。如果您只有几个值要添加,那么所有数据都可以驻留在缓存中,在那里它们可以快速发送到 CPU。已经将数据保存在缓存中的非常非常慢的浮点运算将比需要从系统内存中复制整数的整数运算快很多倍。

我假设您问这个问题是因为您正在开发一个性能关键的应用程序。如果您正在为 x86 架构进行开发,并且需要额外的性能,您可能需要考虑使用 SSE 扩展。这可以大大加快单精度浮点运算,因为可以一次对多个数据执行相同的操作,另外还有一个单独的*组寄存器用于 SSE 操作。 (我注意到在您的第二个示例中您使用了“float”而不是“double”,这让我认为您使用的是单精度数学)。

*注意:使用旧的 MMX 指令实际上会减慢程序速度,因为这些旧指令实际上使用与 FPU 相同的寄存器,因此无法同时使用 FPU 和 MMX。

【讨论】:

  • 在某些处理器上,FP 数学可以比整数数学更快。 Alpha 处理器有 FP 除法指令,但没有整数 1,因此整数除法必须在软件中完成。
  • SSEx 也会加快双精度运算吗?对不起,我对SSE不太熟悉
  • @JohannesSchaub-litb:SSE2(x86-64 的基线)已打包 double-precision FP。每个寄存器只有两个 64 位 doubles,对于矢量化良好的代码,潜在的加速比小于 float。标量 floatdouble 在 x86-64 上使用 XMM 寄存器,旧版 x87 仅用于 long double。 (所以@Dan:不,MMX 寄存器不会与普通 FPU 寄存器冲突,因为 x86-64 上的普通 FPU 是 SSE 单元。MMX 将毫无意义,因为如果你可以做整数 SIMD,你想要 16 字节 xmm0..15而不是 8 字节 mm0..7,并且现代 CPU 的 MMX 比 SSE 吞吐量更差。)
  • 但是 MMX 和 SSE*/AVX2 整数指令确实会竞争相同的执行单元,因此同时使用它们几乎没有用处。只需使用更广泛的 XMM / YMM 版本即可完成更多工作。同时使用 SIMD 整数和 FP 竞争相同的寄存器,但 x86-64 有 16 个。但是总吞吐量限制意味着您无法通过并行使用整数和 FP 执行单元来完成两倍的工作。
【解决方案5】:

我运行了一个测试,它只是将数字加 1 而不是 rand()。结果(在 x86-64 上)是:

  • 短:4.260s
  • int:4.020s
  • 长长:3.350s
  • 浮点数:7.330s
  • 双倍:7.210s

【讨论】:

  • 源码、编译选项、计时方法?我对结果有点惊讶。
  • 与 OP 相同的循环,将“rand( ) % 365”替换为“1”。没有优化。来自“时间”命令的用户时间。
  • “无优化”是关键。你永远不会在优化关闭的情况下进行分析,总是在“发布”模式下进行分析。
  • 但是,在这种情况下,优化关闭会强制执行操作,并且是故意进行的——循环是为了将时间扩大到合理的测量范围。使用常数 1 消除了 rand() 的成本。一个足够聪明的优化编译器会看到 1 添加了 100,000,000 次而无法退出循环,并且只需在单个操作中添加 100000000。那种绕过了整个目的,不是吗?
  • @Stan,使变量易变。即使是智能优化编译器也应该支持多个操作。
【解决方案6】:

定点和浮点数学之间的实际速度可能存在显着差异,但 ALU 与 FPU 的理论最佳情况吞吐量完全不相关。取而代之的是,您的体系结构上的整数和浮点寄存器(实际寄存器,而不是寄存器名称)的数量,您的计算不使用其他方式(例如,用于循环控制),适合缓存行的每种类型的元素数量,考虑到整数与浮点数学的不同语义,可能进行优化——这些效果将占主导地位。您的算法的数据依赖关系在这里发挥着重要作用,因此一般比较无法预测您的问题的性能差距。

例如,整数加法是可交换的,所以如果编译器看到一个循环,就像你在基准测试中使用的那样(假设随机数据是预先准备好的,所以它不会掩盖结果),它可以展开循环并计算没有依赖关系的部分和,然后在循环终止时添加它们。但是对于浮点,编译器必须按照您请求的相同顺序执行操作(您在那里有序列点,因此编译器必须保证相同的结果,这不允许重新排序)所以每个添加都强烈依赖于上一个的结果。

您也可能一次在缓存中容纳更多整数操作数。因此,即使在 FPU 理论上具有更高吞吐量的机器上,定点版本的性能也可能比浮点版本高出一个数量级。

【讨论】:

  • +1 指出由于展开的常量整数运算,幼稚的基准测试如何产生 0 时间循环。此外,如果没有实际使用结果,编译器可以完全丢弃循环(整数或 FP)。
  • 结论是:必须调用一个以循环变量为参数的函数。因为我认为没有编译器能够看到该函数什么都不做,并且可以忽略该调用。由于存在调用开销,因此只有 time == ( float time - integer time ) 的差异才会显着。
  • @GameAlchemist:许多编译器确实消除了对空函数的调用,这是内联的副作用。你必须努力防止这种情况发生。
  • OP 听起来像是在谈论将整数用于 FP 更自然适合的事物,因此需要更多整数代码才能获得与 FP 代码相同的结果。在这种情况下,只需使用 FP。例如,在具有 FPU 的硬件(例如台式机 CPU)上,定点整数 MP3 解码器比浮点解码器慢(并且舍入误差略多)。编解码器的定点实现主要存在于精简的 ARM CPU 上,没有 FP 硬件,只有慢速模拟 FP。
  • 第一点的一个例子:在带有 AVX-512 的 x86-64 上只有 16 个 GP 寄存器,但有 32 个 zmm 寄存器,因此标量浮点数学可能更快
【解决方案7】:

例如(数字越小速度越快),

64 位 Intel Xeon X5550 @ 2.67GHz,gcc 4.1.2 -O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

32 位双核 AMD Opteron(tm) 处理器 265 @ 1.81GHz,gcc 3.4.6 -O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

作为Dan pointed out,即使您对时钟频率进行了标准化(这在流水线设计中本身可能会产生误导),结果会因 CPU 架构而有很大差异(个人 ALU /FPU 性能以及superscalar 设计中每个核心可用的实际ALU/FPU 数量,这会影响independent operations can execute in parallel 的数量——下面的代码没有执行后一个因素,因为下面的所有操作都是顺序相关的。)

穷人的FPU/ALU运算基准:

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}

【讨论】:

  • 为什么混合使用 mult 和 div?如果 mult 可能(或预期?)比 div 快得多,这不是很有趣吗?
  • 在整数和浮点情况下,乘法比除法快得多。除法性能还取决于数字的大小。我通常假设除法要慢约 15 倍。
  • pastebin.com/Kx8WGUfg 我采用了您的基准测试并将每个操作分离到自己的循环中,并添加了volatile 以确保。在 Win64 上,FPU 未使用,MSVC 不会为其生成代码,因此它在那里使用mulssdivss XMM 指令进行编译,这比 Win32 中的 FPU 快 25 倍。测试机为 Core i5 M 520 @ 2.40GHz
  • @JamesDunne 请小心,因为 fp ops v 将很快达到 0 或 +/-inf 非常非常快,这可能会或可能不会(理论上)被视为特殊情况/快速路径通过某些 fpu 实现。
  • 这个“基准”对于乱序执行没有数据并行性,因为每个操作都使用同一个累加器 (v)。在最近的 Intel 设计中,完全没有流水线除法(divss/divps 有 10-14 个周期延迟,以及相同的倒数吞吐量)。 mulss 然而是 5 个周期的延迟,但每个周期可以发出一个。 (或者在 Haswell 上每个周期两个,因为端口 0 和端口 1 都有一个 FMA 乘数)。
【解决方案8】:

需要考虑的两点 -

现代硬件可以重叠指令、并行执行它们并重新排序以充分利用硬件。而且,任何重要的浮点程序也可能有重要的整数工作,即使它只是计算数组、循环计数器等的索引,所以即使你有一个缓慢的浮点指令,它也可能在单独的硬件上运行与一些整数工作重叠。我的观点是,即使浮点指令比整数指令慢,您的整个程序也可能运行得更快,因为它可以利用更多的硬件。

与往常一样,唯一确定的方法是分析您的实际程序。

第二点是,如今大多数 CPU 都有用于浮点的 SIMD 指令,可以同时对多个浮点值进行操作。例如,您可以将 4 个浮点数加载到单个 SSE 寄存器中,并对它们全部并行执行 4 次乘法。如果您可以重写部分代码以使用 SSE 指令,那么它似乎可能会比整数版本更快。 Visual c++ 提供了编译器内部函数来执行此操作,请参阅http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx 了解一些信息。

【讨论】:

  • 需要注意的是,在 Win64 上,MSVC 编译器不再生成 FPU 指令。浮点总是在那里使用 SIMD 指令。这导致 Win32 和 Win64 在 flops 方面的速度差异很大。
【解决方案9】:

今天,整数运算通常比浮点运算快一点。因此,如果您可以在整数和浮点中使用相同的运算进行计算,请使用整数。但是您说“这会导致很多烦人的问题并添加很多烦人的代码”。听起来您需要更多操作,因为您使用整数算术而不是浮点数。在这种情况下,浮点会运行得更快,因为

  • 一旦你需要更多的整数运算,你可能需要更多,所以轻微的速度优势被额外的运算所吞噬

  • 浮点代码更简单,这意味着编写代码更快,这意味着如果它对速度至关重要,您可以花更多时间优化代码。

【讨论】:

  • 这里有很多疯狂的猜测,没有考虑硬件中存在的任何次要影响,这些次要影响通常支配计算时间。不错的起点,但需要通过分析对每个特定应用程序进行检查,而不是作为福音来教导。
【解决方案10】:

如果没有余数运算,浮点版本会慢很多。由于所有的加法都是顺序的,cpu 将无法并行化求和。延迟将是至关重要的。 FPU 加法延迟通常为 3 个周期,而整数加法为 1 个周期。但是,余数运算符的除法器可能是关键部分,因为它在现代 cpu 上没有完全流水线化。所以,假设除法/余数指令会消耗大部分时间,加法延迟造成的差异会很小。

【讨论】:

    【解决方案11】:

    TIL 这变化很大(很多)。以下是使用 gnu 编译器的一些结果(顺便说一句,我还通过在机器上编译进行了检查,来自 xenial 的 gnu g++ 5.4 比来自 linaro 的 4.6.3 精确得多)

    Intel i7 4700MQ xenial

    short add: 0.822491
    short sub: 0.832757
    short mul: 1.007533
    short div: 3.459642
    long add: 0.824088
    long sub: 0.867495
    long mul: 1.017164
    long div: 5.662498
    long long add: 0.873705
    long long sub: 0.873177
    long long mul: 1.019648
    long long div: 5.657374
    float add: 1.137084
    float sub: 1.140690
    float mul: 1.410767
    float div: 2.093982
    double add: 1.139156
    double sub: 1.146221
    double mul: 1.405541
    double div: 2.093173
    

    Intel i3 2370M 也有类似的结果

    short add: 1.369983
    short sub: 1.235122
    short mul: 1.345993
    short div: 4.198790
    long add: 1.224552
    long sub: 1.223314
    long mul: 1.346309
    long div: 7.275912
    long long add: 1.235526
    long long sub: 1.223865
    long long mul: 1.346409
    long long div: 7.271491
    float add: 1.507352
    float sub: 1.506573
    float mul: 2.006751
    float div: 2.762262
    double add: 1.507561
    double sub: 1.506817
    double mul: 1.843164
    double div: 2.877484
    

    Intel(R) Celeron(R) 2955U(Acer C720 Chromebook 运行 xenial)

    short add: 1.999639
    short sub: 1.919501
    short mul: 2.292759
    short div: 7.801453
    long add: 1.987842
    long sub: 1.933746
    long mul: 2.292715
    long div: 12.797286
    long long add: 1.920429
    long long sub: 1.987339
    long long mul: 2.292952
    long long div: 12.795385
    float add: 2.580141
    float sub: 2.579344
    float mul: 3.152459
    float div: 4.716983
    double add: 2.579279
    double sub: 2.579290
    double mul: 3.152649
    double div: 4.691226
    

    DigitalOcean 1GB Droplet Intel(R) Xeon(R) CPU E5-2630L v2(运行可靠)

    short add: 1.094323
    short sub: 1.095886
    short mul: 1.356369
    short div: 4.256722
    long add: 1.111328
    long sub: 1.079420
    long mul: 1.356105
    long div: 7.422517
    long long add: 1.057854
    long long sub: 1.099414
    long long mul: 1.368913
    long long div: 7.424180
    float add: 1.516550
    float sub: 1.544005
    float mul: 1.879592
    float div: 2.798318
    double add: 1.534624
    double sub: 1.533405
    double mul: 1.866442
    double div: 2.777649
    

    AMD Opteron(tm) 处理器 4122(精确)

    short add: 3.396932
    short sub: 3.530665
    short mul: 3.524118
    short div: 15.226630
    long add: 3.522978
    long sub: 3.439746
    long mul: 5.051004
    long div: 15.125845
    long long add: 4.008773
    long long sub: 4.138124
    long long mul: 5.090263
    long long div: 14.769520
    float add: 6.357209
    float sub: 6.393084
    float mul: 6.303037
    float div: 17.541792
    double add: 6.415921
    double sub: 6.342832
    double mul: 6.321899
    double div: 15.362536
    

    这使用来自http://pastebin.com/Kx8WGUfg 的代码作为benchmark-pc.c

    g++ -fpermissive -O3 -o benchmark-pc benchmark-pc.c
    

    我已经运行了多次,但这似乎是一般数字相同的情况。

    一个值得注意的例外似乎是 ALU mul 与 FPU mul。加法和减法似乎微不足道。

    这是上面的图表形式(点击查看完整尺寸,较低的速度更快,更可取):

    更新以适应@Peter Cordes

    https://gist.github.com/Lewiscowles1986/90191c59c9aedf3d08bf0b129065cccc

    i7 4700MQ Linux Ubuntu Xenial 64 位(适用于 2018-03-13 的所有补丁)
        short add: 0.773049
        short sub: 0.789793
        short mul: 0.960152
        short div: 3.273668
          int add: 0.837695
          int sub: 0.804066
          int mul: 0.960840
          int div: 3.281113
         long add: 0.829946
         long sub: 0.829168
         long mul: 0.960717
         long div: 5.363420
    long long add: 0.828654
    long long sub: 0.805897
    long long mul: 0.964164
    long long div: 5.359342
        float add: 1.081649
        float sub: 1.080351
        float mul: 1.323401
        float div: 1.984582
       double add: 1.081079
       double sub: 1.082572
       double mul: 1.323857
       double div: 1.968488
    
    AMD Opteron(tm) 处理器 4122(精确,DreamHost 共享主机)
        short add: 1.235603
        short sub: 1.235017
        short mul: 1.280661
        short div: 5.535520
          int add: 1.233110
          int sub: 1.232561
          int mul: 1.280593
          int div: 5.350998
         long add: 1.281022
         long sub: 1.251045
         long mul: 1.834241
         long div: 5.350325
    long long add: 1.279738
    long long sub: 1.249189
    long long mul: 1.841852
    long long div: 5.351960
        float add: 2.307852
        float sub: 2.305122
        float mul: 2.298346
        float div: 4.833562
       double add: 2.305454
       double sub: 2.307195
       double mul: 2.302797
       double div: 5.485736
    
    Intel Xeon E5-2630L v2 @ 2.4GHz (Trusty 64-bit, DigitalOcean VPS)
        short add: 1.040745
        short sub: 0.998255
        short mul: 1.240751
        short div: 3.900671
          int add: 1.054430
          int sub: 1.000328
          int mul: 1.250496
          int div: 3.904415
         long add: 0.995786
         long sub: 1.021743
         long mul: 1.335557
         long div: 7.693886
    long long add: 1.139643
    long long sub: 1.103039
    long long mul: 1.409939
    long long div: 7.652080
        float add: 1.572640
        float sub: 1.532714
        float mul: 1.864489
        float div: 2.825330
       double add: 1.535827
       double sub: 1.535055
       double mul: 1.881584
       double div: 2.777245
    

    【讨论】:

    • gcc5 可能会自动矢量化 gcc4.6 没有的东西? benchmark-pc 是否测量吞吐量和延迟的某种组合?在您的 Haswell (i7 4700MQ) 上,整数乘法是每个时钟吞吐量 1,3 个周期延迟,但整数加/减是每个时钟吞吐量 4,1 个周期延迟 (agner.org/optimize)。因此,大概有很多循环开销会稀释这些数字以使 add 和 mul 如此接近(long add: 0.824088 vs. long mul: 1.017164)。 (gcc 默认不展开循环,除非完全展开非常低的迭代次数)。
    • 顺便说一句,为什么它不测试int,只测试shortlong?在 Linux x86-64 上,short 是 16 位(因此在某些情况下会降低部分寄存器的速度),而 longlong long 都是 64 位类型。 (也许它是为 x86-64 仍然使用 32 位 long 的 Windows 设计的?或者它是为 32 位模式设计的。)在 Linux 上,the x32 ABI has 32-bit long in 64-bit mode,所以如果您安装了库,请使用 gcc -mx32 来ILP32 的编译器。或者只使用-m32 并查看long 数字。
    • 你真的应该检查你的编译器是否自动矢量化了任何东西。例如在 xmm 寄存器上使用 addps 而不是 addss,在一条指令中并行执行 4 FP 添加,其速度与标量 addss 一样快。 (使用-march=native 允许使用您的 CPU 支持的任何指令集,而不仅仅是 x86-64 的 SSE2 基线)。
    • @cincodenada 请让图表显示完整的 15 个,因为它可以说明性能。
    • @PeterCordes 我明天试试看,谢谢你的用心。
    猜你喜欢
    • 1970-01-01
    • 2018-03-22
    • 2012-06-10
    • 1970-01-01
    • 2013-01-31
    • 1970-01-01
    • 2013-04-01
    • 2013-06-12
    相关资源
    最近更新 更多