如何测量短段C/汇编代码的速度?

如何测量短段C/汇编代码的速度?,c,linux,performance,assembly,C,Linux,Performance,Assembly,考虑以下C代码: #include <complex.h> complex float f(complex float x[]) { complex float p = 1.0; for (int i = 0; i < 32; i++) p += x[i]; return p; } gcc第7版(快照)带有-O3-march=core-avx2-ffast math给出: f: vmovups ymm1, YMMWORD PTR [rd

考虑以下C代码:

#include <complex.h>
complex float f(complex float x[]) {
  complex float p = 1.0;
  for (int i = 0; i < 32; i++)
    p += x[i];
  return p;
}
gcc第7版(快照)带有
-O3-march=core-avx2-ffast math
给出:

f:
        vmovups   ymm1, YMMWORD PTR [rdi]                       #5.10
        vmovups   ymm2, YMMWORD PTR [64+rdi]                    #5.10
        vmovups   ymm5, YMMWORD PTR [128+rdi]                   #5.10
        vmovups   ymm6, YMMWORD PTR [192+rdi]                   #5.10
        vmovsd    xmm0, QWORD PTR p.152.0.0.1[rip]              #3.19
        vaddps    ymm3, ymm1, YMMWORD PTR [32+rdi]              #3.19
        vaddps    ymm4, ymm2, YMMWORD PTR [96+rdi]              #3.19
        vaddps    ymm7, ymm5, YMMWORD PTR [160+rdi]             #3.19
        vaddps    ymm8, ymm6, YMMWORD PTR [224+rdi]             #3.19
        vaddps    ymm9, ymm3, ymm4                              #3.19
        vaddps    ymm10, ymm7, ymm8                             #3.19
        vaddps    ymm11, ymm9, ymm10                            #3.19
        vextractf128 xmm12, ymm11, 1                            #3.19
        vaddps    xmm13, xmm11, xmm12                           #3.19
        vmovhlps  xmm14, xmm13, xmm13                           #3.19
        vaddps    xmm15, xmm13, xmm14                           #3.19
        vaddps    xmm0, xmm15, xmm0                             #3.19
        vzeroupper                                              #6.10
        ret                                                     #6.10
f:
        lea     r10, [rsp+8]
        and     rsp, -32
        push    QWORD PTR [r10-8]
        push    rbp
        mov     rbp, rsp
        push    r10
        vmovups ymm0, YMMWORD PTR [rdi+64]
        vmovaps ymm1, YMMWORD PTR .LC0[rip]
        vaddps  ymm0, ymm0, YMMWORD PTR [rdi+32]
        vaddps  ymm1, ymm1, YMMWORD PTR [rdi]
        vaddps  ymm0, ymm0, ymm1
        vmovups ymm1, YMMWORD PTR [rdi+128]
        vaddps  ymm1, ymm1, YMMWORD PTR [rdi+96]
        vaddps  ymm0, ymm0, ymm1
        vmovups ymm1, YMMWORD PTR [rdi+192]
        vaddps  ymm1, ymm1, YMMWORD PTR [rdi+160]
        vaddps  ymm0, ymm0, ymm1
        vaddps  ymm0, ymm0, YMMWORD PTR [rdi+224]
        vunpckhps       xmm3, xmm0, xmm0
        vshufps xmm2, xmm0, xmm0, 255
        vshufps xmm1, xmm0, xmm0, 85
        vaddss  xmm1, xmm2, xmm1
        vaddss  xmm3, xmm3, xmm0
        vextractf128    xmm0, ymm0, 0x1
        vunpckhps       xmm4, xmm0, xmm0
        vshufps xmm2, xmm0, xmm0, 85
        vaddss  xmm4, xmm4, xmm0
        vshufps xmm0, xmm0, xmm0, 255
        vaddss  xmm0, xmm2, xmm0
        vaddss  xmm3, xmm3, xmm4
        vaddss  xmm1, xmm1, xmm0
        vmovss  DWORD PTR [rbp-24], xmm3
        vmovss  DWORD PTR [rbp-20], xmm1
        vzeroupper
        vmovq   xmm0, QWORD PTR [rbp-24]
        pop     r10
        pop     rbp
        lea     rsp, [r10-8]
        ret 
我对哪一个更快感兴趣,所以测量运行时间会很好

但是,我不知道如何度量花费这么少时间的代码的运行时间

哪种代码更快?如何可靠地测量它


您需要一个能够多次调用此函数的测试线束

这将使运行时达到一个非常重要的级别,并将平均操作系统调度引起的任何差异

void test_f() 
{
    complex float x[32] = { 1+2i, 2+3i };    // add as many as needed. 
                                             // here i is a special
                                             // constant for complex numbers
    int i;
    for (i=0; i<10000000; i++) {
        f(x);
    }
}
void test_f()
{
复数浮点x[32]={1+2i,2+3i};//根据需要添加任意数量的浮点。
//这里我是一个特殊的
//复数常数
int i;

对于(i=0;i重复次数足够多,需要足够长的时间。太短会使其更容易出现轻微的计时异常(测量时间的开销,指令在时间上跳跃,通过OoOE将指令读入或读出有效计时区域,等等),太长。除非你是一个纯粹主义者,否则这并不重要。你通常可以猜测实际时间应该是多少,由于中断等原因,测量的时间会稍微高一点,但在调整时钟速度后,你应该会得到一个“接近”合理值的结果(例如,对于延迟测量,您应该获得整数个周期)。执行多次运行并绘制它,忽略奇怪的异常值,尤其是在顶部

确保您处于turbo模式(或在BIOS设置中禁用所有频率缩放),并且矢量单元在计时之前处于“唤醒”(对于AVX代码)状态,因此请进行一些预热。您打算计时的代码也可以这样做

请非常小心,您有意选择使迭代依赖(测量延迟)或独立(测量吞吐量),不要只是做一些随机的事情-你会有一个你不知道的度量值,即延迟或吞吐量。也不要让编译器优化它,将对你正在度量的东西的一些或所有调用都去掉,这样你就没有度量值了

您可以使用
rdtscp
进行计时本身,或者使用精度较低的东西-精度越低,计时循环需要的时间越长。您可以从绘图的外观大致判断精度,如果它看起来高度离散,并且所有东西都排列在几个“箱子”中,请使用更多的迭代(或更好的时间度量)

如果您打算在特定的缓存条件下进行测量,那么它会变得更加棘手,因为设置该状态也需要时间,因此它会有点像“猜测开销”(很难精确测量)的游戏


FWIW ICC asm看起来更快,GCC正在进行大量的标量计算。

无论何时,只要您想要执行性能度量,都需要执行以下几个步骤:

  • 经常重复要测量的操作,以达到您可以精确测量的次数。通常一到十秒就足够了。在几乎所有情况下,都可以通过简单的循环来实现

  • 防止优化器由于未使用的结果而优化重复或完全优化测量的操作

    对此,有几种可能的方法:

    • 在每次迭代中修改输入并实际使用所有结果。在您的情况下,这可能类似于:

      complex float accu = 0+0i;
      for(int i = 0; i < 100000000; i++) {
          x[i%32] += 42+3i;   //different input on each pass
          accu += f(x);
      }
      printComplex(accu);    //this depends on the output of all passes
      

    • 优化器不会消除循环吗?对于gcc,您可以使用
      \uuuuuu属性(优化(0)))
      ,以防止对测试函数进行优化。或者,您将测试函数放在使用不同优化设置编译的不同.c文件中。如果您关闭优化,则不会得到程序集,除非我有误解。请明确说明其他解决方案的工作方式好吗?@elean或者将测试线束放入一个文件
      harnese.c
      ,并将要测试的函数放入一个文件
      function.c
      。如果愿意,可以使用
      -O3
      编译这两个文件。然后将结果链接在一起而不进行优化。这样,优化器就看不到调用
      f(x)的情况
      在测试线束中多次没有意义,不会优化掉回路。但是,它会优化
      f()
      尽可能好。@cmaster听起来不错。你能用一些命令行示例添加一个答案,让我接受吗?如果你只是重复它,如何避免优化器消除循环?@eleanora你可以将函数放在不同的编译单元中(然后不使用LTO)(然后必须在循环中调用它,因为优化器必须假设函数可能会做一些有趣的事情),或者只是在汇编中编写循环,并确保它完全符合您的要求。您是否能够显示这些想法所需的代码?如果可以在汇编中添加循环,看起来是最好的解决方案,但我不知道如何混合C和汇编。@eleanora我可以展示一些示例,但这取决于平台的详细信息(还有汇编器)。不过,将C和汇编结合起来并不难,只要将/assembly编译成对象文件,并像往常一样用链接器将它们链接在一起即可。从汇编中调用C函数需要正确的名称(可能需要在函数名称前加下划线)和某种形式的
      extern
      声明(汇编程序之间的语法不同)谢谢。我在linux上,很高兴使用对您最方便的任何工具。我认为我粘贴的程序集应该使用
      gas
      编译/assembe。您使用这个库:如何使用完全优化编译,但不使用优化链接?您只需在编译时使用
      gcc-O3
      ,然后使用
      gcc
      when链接。链接器通常只将编译过程中创建的符号视为黑匣子。但是
      complex float f(complex float x[]) {
          complex float p = 1.0;
          return p;
      }