Performance 尽管迭代之间存在依赖关系,但循环所需时间少于1个周期

Performance 尽管迭代之间存在依赖关系,但循环所需时间少于1个周期,performance,x86,intel,rdtsc,Performance,X86,Intel,Rdtsc,我想在Skylake i5-6500 CPU上测试一次添加所需的时间。C对我来说已经足够低级了,所以我编写了以下代码: //初始化内容 int a=兰特; int b=兰德; 常量无符号长循环计数=100000000; 忽略无符号整数;//用于uu rdtscp //预热任何需要预热的东西 对于int i=0;i

我想在Skylake i5-6500 CPU上测试一次添加所需的时间。C对我来说已经足够低级了,所以我编写了以下代码:

//初始化内容 int a=兰特; int b=兰德; 常量无符号长循环计数=100000000; 忽略无符号整数;//用于uu rdtscp //预热任何需要预热的东西 对于int i=0;i<100000;i++{ asm volatile:+r a;//防止Clang用乘法替换循环 a+=b; } //实际测量 uint64_t定时器=u rdtscp&忽略; 对于无符号长i=0;i
# %bb.2:
    rdtscp
    movq    %rdx, %rdi
    movl    %ecx, 4(%rsp)
    shlq    $32, %rdi
    orq %rax, %rdi
    movl    $1000000000, %eax       # imm = 0x3B9ACA00
    .p2align    4, 0x90
.LBB0_3:                                # =>This Inner Loop Header: Depth=1
    #APP
    #NO_APP
    addl    %esi, %ebx
    addq    $-1, %rax
    jne .LBB0_3
# %bb.4:
    rdtscp
并运行此代码输出

0.94 cycles/iteration
或者一个几乎总是在0.93和0.96之间的数字

我感到惊讶的是,这个循环可以在不到1个周期/迭代的时间内执行,因为存在对a的数据依赖,这应该会阻止a+=b的并行执行

IACA还确认预期吞吐量为0.96个周期。另一方面,llvm mca预测总共有104个周期来执行100次循环迭代。我可以编辑的痕迹,如果需要;让我知道

当我使用SSE寄存器而不是通用寄存器时,我观察到类似的行为

我可以想象,CPU足够聪明,可以注意到b是常数,因为加法是可交换的,所以它可以展开循环并以某种方式优化加法。然而,我从来没有听说过也没有读过任何关于这方面的东西。而且,如果是这样的话,我会期望更好的性能,即更少的周期/迭代比0.94个周期/迭代

发生了什么事?这个循环如何能够在每次迭代不到1个周期的时间内执行

为了完整起见,需要一些背景知识。如果您对我为什么尝试对单个添加进行基准测试不感兴趣,请忽略问题的剩余部分


我知道有一些工具,比如llvm注释,被设计用来对一条指令进行基准测试,我应该取代它们,或者只是看看agner fog的文档。然而,我实际上在尝试:一个在循环中做一个加法,我的问题的对象;一个是在SSE寄存器上对每个循环进行3次加法,这将最大限度地提高端口使用率,而不受数据依赖性的限制,另一个是将加法作为软件中的电路来实现。虽然结果基本上和我预期的一样;在循环中添加一个元素的版本的0.94个循环/迭代让我感到困惑。

核心频率和TSC频率可能不同。您的循环预期在每次迭代中运行1个核心周期。如果在循环执行期间,核心频率恰好是TSC频率的两倍,则吞吐量将为每次迭代0.5个TSC周期,这相当于每次迭代1个核心周期

在您的情况下,平均核心频率似乎略高于TSC频率。如果在做实验时不想考虑动态频率缩放,那么只需将核心频率固定为等于TSC频率就更容易了,这样就不必转换数字。否则,您还必须测量核心频率的平均值

在支持单核频率缩放的处理器上,您必须固定所有核上的频率,或者将实验固定到具有固定频率的单个核上。或者,您可以使用诸如perf之类的工具轻松测量以核心周期或秒为单位的时间,而不是以TSC周期为单位进行测量


另请参见:.

核心频率和TSC频率可以不同。您的循环预期在每次迭代中运行1个核心周期。如果在循环执行期间,核心频率恰好是TSC频率的两倍,则吞吐量将为每次迭代0.5个TSC周期,这相当于每次迭代1个核心周期

在您的情况下,平均核心频率似乎略高于TSC频率。如果在做实验时不想考虑动态频率缩放,那么只需将核心频率固定为等于TSC频率就更容易了,这样就不必转换数字。否则,您还必须测量核心频率的平均值

在支持单核频率缩放的处理器上,您必须固定所有核上的频率,或者将实验固定到具有固定频率的单个核上。或者,您可以使用诸如perf之类的工具轻松测量以核心周期或秒为单位的时间,而不是以TSC周期为单位进行测量


另请参见:。

芯片上的TSC频率是多少?运行dmesg | grep'tsc'以了解情况。核心频率是多少?核心频率是固定的吗?@HadiBrais捕捉得很好。修理
通过调整CPU频率解决了这个问题。我认为将cpufreq调控器设置为performance就足够了。但我已经将频率设置为3.2GHz CPU电源频率设置-F3.2G,现在我得到了1.00个循环/迭代,正如预期的那样。想把它作为一个答案吗;可以作为我的答案中解释的参考周期中RDTSC计数的副本关闭。可能会有一个更具体的问题,这个问题更像你的问题,答案就集中在这一点上。关于RDTSC是如何工作的,这是一个众所周知的事实,所以它似乎不值得一个长的答案,但是如果有人想写一个,可能需要一个带链接的短的答案。你芯片上的TSC频率是多少?运行dmesg | grep'tsc'以了解情况。核心频率是多少?核心频率是固定的吗?@HadiBrais捕捉得很好。修复CPU频率解决了这个问题。我认为将cpufreq调控器设置为performance就足够了。但我已经将频率设置为3.2GHz CPU电源频率设置-F3.2G,现在我得到了1.00个循环/迭代,正如预期的那样。想把它作为一个答案吗;可以作为我的答案中解释的参考周期中RDTSC计数的副本关闭。可能会有一个更具体的问题,这个问题更像你的问题,答案就集中在这一点上。关于RDTSC是如何工作的,这是一个众所周知的事实,因此似乎不值得一个冗长的回答,但是如果有人想写的话,可能会有一个带有链接的短的。Re:首先使用perf来测量核心时钟周期:我经常在一个程序上使用perf stat,这个程序的工作负载在一个大的重复循环中,所以在运行100毫秒到1秒钟时,它完全控制了启动开销,如果你想要更高的准确度,可以选择更长的时间。但是,如果您的循环瓶颈导致每次迭代都有一个完整的周期,那么您真的不需要麻烦。理想的静态链接方式,在asm中编写时很容易,或者在C中编写一个随asm退出的\u开始。或者更高级的选项是设置一些东西,这样您就可以从用户空间中使用rdpmc了。@PeterCordes在这种情况下,您为什么不麻烦呢?因为由于有效载荷中循环的整数,数字总是像a,aaa,000,xxx,你可以只看a,aaa部分?@BeeOnRope:是的,没错。如果你可以假设周期/iter的非整数部分只是噪声,那么即使是10毫秒的定时运行也是可以的。如果你没有做内存的事情,需要在第一次通过多个页面时出现页面错误。。。在这种情况下,假设一个整数瓶颈首先是一个错误的假设。内存访问使一切变得复杂。Re:首先使用perf来测量核心时钟周期:我经常在一个程序上使用perf stat,该程序的工作负载在一个大的重复循环中,因此它在运行100ms到1秒时完全控制启动开销,如果您想获得更高的精度,则可以使用更长的时间。但是,如果您的循环瓶颈导致每次迭代都有一个完整的周期,那么您真的不需要麻烦。理想的静态链接方式,在asm中编写时很容易,或者在C中编写一个随asm退出的\u开始。或者更高级的选项是设置一些东西,这样您就可以从用户空间中使用rdpmc了。@PeterCordes在这种情况下,您为什么不麻烦呢?因为由于有效载荷中循环的整数,数字总是像a,aaa,000,xxx,你可以只看a,aaa部分?@BeeOnRope:是的,没错。如果你可以假设周期/iter的非整数部分只是噪声,那么即使是10毫秒的定时运行也是可以的。如果你没有做内存的事情,需要在第一次通过多个页面时出现页面错误。。。在这种情况下,假设一个整数瓶颈首先是一个错误的假设。内存访问使一切变得复杂。