【问题标题】:Too much cycles循环次数过多
【发布时间】:2016-04-22 18:57:03
【问题描述】:

我考虑了来自 Agner Fog 的 Optimizing Assembly 的一个例子。他测试:

示例 12.6b。 DAXPY 算法,32 位模式

L1:
    movapd xmm1, [esi+eax]   ; X[i], X[i+1]
    mulpd  xmm1, xmm2        ; X[i] * DA, X[i+1] * DA
    movapd xmm0, [edi+eax]   ; Y[i], Y[i+1]
    subpd  xmm0, xmm1        ; Y[i]-X[i]*DA, Y[i+1]-X[i+1]*DA
    movapd [edi+eax], xmm0   ; Store result
    add eax, 16              ; Add size of two elements to index
    cmp eax, ecx             ; Compare with n*8
    jl L1                    ; Loop back

他估计在 Pentium M 上大约 6 个循环/迭代。我尝试在我的 CPU-Ivy Bridge 上做同样的事情。我每次迭代实现了 3 个周期,但从我在纸上的计算来看,有可能得到 2 个周期。也不知道是不是我在理论计算上犯了错误,还是可以改进的。

我做了什么:

来自http://www.realworldtech.com/sandy-bridge/5/ 我知道我的 CPU 可以退出 4 个 microops 循环,所以它不是瓶颈。

#uops fused = 8 / 4 = 2

所以2 是我们当前的瓶颈。让我们看看另一种可能性:

Microps 有模式:1-1-1-1-1-1-1-1 并且根据 Agner Fog 我的 CPU 有模式 1-1-1-1(和其他)。从中我们可以看出,可以在 2 个周期内解码指令。这不是瓶颈。此外,SnB cpus 具有微缓存,因此获取和解码都不是瓶颈。

以字节为单位的指令大小为 32,因此它适合微缓存窗口(32 字节)。

根据我的实验,当我添加 nop 指令时,它会增加每次迭代的周期数(大约 0.5 个周期)。

所以,问题是:

ONE 循环在哪里? :D

【问题讨论】:

    标签: optimization x86


    【解决方案1】:

    您确定不受内存带宽的限制吗?您需要使用适合 L1 缓存的数组进行测试,因为您依赖于每 2 个时钟的两次负载和一次存储。 (这是 IvB 理论最大值的一半以上,即每个时钟两个 128b 内存操作,其中最多一个是存储。)


    解码不相关,因为您的循环适合循环缓冲区。 (小于 28 微秒)。所以它只是以 4 个已解码的微指令为一组发出。


    您的融合域 uop 计数错误。 cmp/jl 可以宏融合成一个比较和分支微指令。但是,由于 Agner Fog 的指南中没有出现的另一个错误(目前),该错误被另一个错误所平衡。

    movapd [edi+eax], xmm0 不能在 IvB 上进行微融合,所以它是 2 个融合域微指令。

    SnB-family CPUs can only micro-fuse memory operands that don't use an index register.我最近在英特尔的优化手册中找到了官方确认,它解释了 Agner 测试与我的测试的不同结果:这种寻址模式可以在解码器和 uop 缓存中进行微融合,但在 OOO 内核中则不能。 (请参阅我对那个问题的回答。我应该给 Agner 发送另一条消息,让他知道英特尔的文档解决了我们的困惑......)

    试试这个:

        add    ecx, edi          ; end-of-Y pointer
        sub    esi, edi          ; esi = X-Y
    L1:
        movapd xmm1, [esi+edi]   ; 1 uop
        mulpd  xmm1, xmm2        ; 1 uop
        movapd xmm0, [edi]       ; 1 uop
        subpd  xmm0, xmm1        ; 1 uop
        movapd [edi], xmm0       ; 1 micro-fused uop
        add edi, 16              ; 1 uop
        cmp edi, ecx             ; loop while (dst < end_Y)
        jb L1                    ; cmp+jb = 1 macro-fused uop
    

    负载不需要微融合,但存储是 2 个融合域微指令。 (存储地址和存储数据)。

    IACA 会告诉你存储是 2 微指令,不能微熔。值得一看。有时它的数字是错误的(例如,它认为shrd 在 SnB 上仍然很慢),但只要您意识到它是对真实硬件行为的简单近似,并且 不是 一个周期,它通常很有用 -准确的模拟器。


    我的版本是总共 7 个融合域微指令。因此,它应该在 SnB 系列 CPU 上每 2 个时钟运行一次迭代。您的原始数据是 8 微秒,因此此更改不会产生任何影响。我在注意到您没有考虑 cmp/jcc 的宏融合之前写了它,所以我认为您的循环实际上是 9 uop。由于添加单个 nop 会减慢您的代码速度,这进一步证明了它是 8 个融合域微指令。如果使用太大的数组进行测试导致缓存未命中并不能解释它,那么 IvB 在调度加载/存储 uops 方面可能做得不好?似乎不太可能,因为它们都必须使用端口 2 或 3 来存储地址或加载微指令。 (在未融合的域中,存储数据 uops 转到端口 4)。

    你确定你的循环真的每 3 个循环就有一次迭代吗?添加nop 会减慢它的速度是没有意义的,因为 9 uop 循环应该在 3 个周期内发出。

    【讨论】:

    • 1.谢谢! :) 2. 你显然是对的。我在 2048 和 4096 个数组元素上测试了我的代码(因此它适合 L1),实际上每次迭代我需要大约 2 个周期。 3.“如果使用太大的数组进行测试导致缓存未命中并不能解释它,”他们确实解释了——当我制作更大的数组时,它会增加每次迭代的周期数。 4. 我还有一个问题,uop 被融合到底是什么意思。我知道字面意思是什么——一些微指令加入了。但是怎么做呢?
    • Agner Fog 的微架构指南和 Intel 的优化手册应该对 uop 微宏融合有一个不错的解释。在 Agner Fog 的 microarch pdf 中,他在为第一个实现融合的 CPU 描述融合时更详细地介绍了融合:Pentium M。参见第 90/91 页:“第 7.6 节微操作融合”大约有 2 页长。我并不是要对每个答案中的所有内容都进行完整的解释,这将花费很长时间。这就是为什么我刚刚链接到关于 SnB 系列 CPU 上索引寻址模式的微融合的其他答案。当然你需要阅读链接。
    • 谢谢。我不要求你向我解释一切。链接,其他参考都可以。
    猜你喜欢
    • 2012-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-06
    • 2014-04-01
    • 1970-01-01
    相关资源
    最近更新 更多