草庐IT

c++ - 为什么 ICC 以这种方式展开这个循环并使用 lea 进行算术运算?

coder 2023-11-15 原文

查看 ICC 17 生成的用于迭代 std::unordered_map<> 的代码(使用 https://godbolt.org )让我很困惑。

我将示例提炼为:

long count(void** x)
{
  long i = 0;
  while (*x)
  {
    ++i;
    x = (void**)*x;
  }
  return i;
}

使用 ICC 17 编译它,使用 -O3 标志,导致以下反汇编:

count(void**):
        xor       eax, eax                                      #6.10
        mov       rcx, QWORD PTR [rdi]                          #7.11
        test      rcx, rcx                                      #7.11
        je        ..B1.6        # Prob 1%                       #7.11
        mov       rdx, rax                                      #7.3
..B1.3:                         # Preds ..B1.4 ..B1.2
        inc       rdx                                           #7.3
        mov       rcx, QWORD PTR [rcx]                          #7.11
        lea       rsi, QWORD PTR [rdx+rdx]                      #9.7
        lea       rax, QWORD PTR [-1+rdx*2]                     #9.7
        test      rcx, rcx                                      #7.11
        je        ..B1.6        # Prob 18%                      #7.11
        mov       rcx, QWORD PTR [rcx]                          #7.11
        mov       rax, rsi                                      #9.7
        test      rcx, rcx                                      #7.11
        jne       ..B1.3        # Prob 82%                      #7.11
..B1.6:                         # Preds ..B1.3 ..B1.4 ..B1.1
        ret                                                     #12.10

与明显的实现(gcc 和 clang 使用的,甚至是 -O3)相比,它似乎做了一些不同的事情:

  1. 它展开循环,在返回之前进行两次递减 - 然而,在这一切的中间有一个条件跳转。
  2. 它使用 lea 进行一些算术计算
  3. 它为 while 循环的每 两次 次迭代保留一个计数器 (inc rdx),并立即为每次迭代计算相应的计数器(进入 rax 和 rsi)

做这一切的潜在好处是什么?我想这可能与日程安排有关?

只是为了比较,这是 gcc 6.2 生成的代码:

count(void**):
        mov     rdx, QWORD PTR [rdi]
        xor     eax, eax
        test    rdx, rdx
        je      .L4
.L3:
        mov     rdx, QWORD PTR [rdx]
        add     rax, 1
        test    rdx, rdx
        jne     .L3
        rep ret
.L4:
        rep ret

最佳答案

这不是一个很好的例子,因为循环会在指针追逐延迟上造成瓶颈,而不是 uop 吞吐量或任何其他类型的循环开销。但在某些情况下,更少的 uops 可能有助于乱序的 CPU 看得更远。 或者我们可以只讨论循环结构的优化并假装它们很重要,例如用于执行其他操作的循环。


展开通常很有用,即使循环行程计数无法提前计算。 (例如,在这样的搜索循环中,当它找到哨兵时停止)。未采用的条件分支与采用的分支不同,因为它对前端没有任何负面影响(当它预测正确时)。

基本上 ICC 只是在展开这个循环时做得很糟糕。它使用 LEA 和 MOV 来处理 i 的方式相当脑残,因为它使用的微指令多于两个 inc rax 指令。 (尽管它确实使关键路径更短,但在具有零延迟 mov r64, r64 的 IvB 和更高版本上,因此乱序执行可以在运行这些 uops 时领先)。

当然,由于这个特定的循环瓶颈会影响指针追逐的延迟,因此您最多只能获得每 4 个时钟一个的长链吞吐量(Skylake 上的 L1 加载使用延迟,对于整数寄存器),或者在大多数其他英特尔微体系结构上每 5 个时钟一个。 (我没有仔细检查这些延迟;不要相信那些特定的数字,但它们差不多是正确的)。

IDK 如果 ICC 分析循环携带的依赖链来决定如何优化。如果是这样,它可能根本就不会展开,如果它在尝试展开时知道自己做得不好的话。

对于短链,无序执行可能能够在循环之后开始运行某些东西,如果循环导出分支预测正确。在这种情况下,优化循环很有用。

展开还会针对该问题抛出更多分支预测器条目。您有两个分支,而不是一个具有长模式的循环退出分支(例如,在 15 个被采用后未被采用)。对于同一个例子,一个从未被采用,一个被采用了 7 次然后第 8 次没有被采用。


这是手写的二元展开实现的样子:

在其中一个退出点的循环退出路径中修复 i,这样您就可以在循环内廉价地处理它。

count(void**):
    xor       eax, eax              # counter
    mov       rcx, QWORD PTR [rdi]  # *x
    test      rcx, rcx
    je        ..B1.6
.p2align 4   # mostly to make it more likely that the previous test/je doesn't decode in the same block at the following test/je, so it doesn't interfere with macro-fusion on pre-HSW
.loop:
    mov       rcx, QWORD PTR [rcx]
    test      rcx, rcx
    jz        .plus1

    mov       rcx, QWORD PTR [rcx]
    add       rax, 2
    test      rcx, rcx
    jnz       .loop
..B1.6:
    ret

.plus1:           # exit path for odd counts
    inc       rax
    ret

如果两个 TEST/JCC 对宏融合,这会使循环体有 5 个融合域微指令。 Haswell 可以在单个解码组中进行两次融合,但早期的 CPU 不能。

gcc 的执行只有 3 uops,小于 CPU 的 issue width。参见 this Q&A关于从循环缓冲区发出的小循环。没有 CPU 实际上可以每个时钟执行/退出超过一个采用的分支,因此不容易测试 CPU 如何发出小于 4 微指令的循环,但显然 Haswell 可以每 1.25 个周期发出一个 5 微指令循环。早期的 CPU 可能只会每 2 个周期发出一次。

关于c++ - 为什么 ICC 以这种方式展开这个循环并使用 lea 进行算术运算?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41081115/

有关c++ - 为什么 ICC 以这种方式展开这个循环并使用 lea 进行算术运算?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐