【问题标题】:Intel JCC Erratum - what is the effect of prefixes used for mitigation?英特尔 JCC 勘误表 - 用于缓解的前缀有什么影响?
【发布时间】:2023-03-31 09:16:01
【问题描述】:

Intel recommends 使用指令前缀来减轻 JCC Erratum 的性能影响。

如果使用/QIntel-jcc-erratum 编译,MSVC 会遵循建议,并插入前缀指令,如下所示:

3E 3E 3E 3E 3E 3E 3E 3E 3E 48 8B C8   mov rcx,rax ; with redundant 3E prefixes

They say MSVC 在前缀不可用时诉诸 NOP。

Clang 对此有 -mbranches-within-32B-boundaries 选项,它更喜欢 nop,如果需要,可以使用多字节(https://godbolt.org/z/399nc5Msq 通知 xchg ax, ax

3E前缀的后果是什么,具体来说:

  • 为什么英特尔推荐这个,而不是多字节 NOP?
  • 不受影响的 CPU 会有什么后果?
  • 据报道,一个程序在 AMD 上使用/QIntel-jcc-erratum 运行得更快,有什么可能的解释?

【问题讨论】:

  • NOP 是一条单独的指令,必须单独解码并通过管道。 What methods can be used to efficiently extend instruction length on modern x86?。您应该始终使用前缀填充指令以实现所需的对齐,而不是插入 NOP。可能英特尔认为值得付出努力,因为这实际上是 inside 内部循环,而不仅仅是内部循环之外的 NOP。
  • 但请注意,某些 CPU 不能有效地解码一条指令上超过 3 个前缀,因此这可能就是为什么 this 缓解 JCC 勘误表的策略未启用的原因默认。您希望将填充分布在多个先前的指令上,以避免 Silvermont 系列(例如 Gracemont)的瓶颈(例如,Alder Lake E-cores 突然使该系列与主流相关)。我忘记了 AMD 解码限制。
  • IIRC,GNU 工具链在汇编程序中进行了缓解,所以寻找as 选项(你可以让GCC 与-Wa,-... 一起使用)GCC 不知道指令大小,它只知道打印文本。这就是为什么它需要 GAS 支持像 .p2align 4,,10 这样的东西来对齐 16,如果这将需要少于 10 个字节的填充,以实现它想要使用的对齐启发式。 (通常后跟.p2align 3 以无条件对齐 8。)
  • 发现博客文章提到尺寸影响为 3%,性能影响为可忽略devblogs.microsoft.com/cppblog/jcc-erratum-mitigation-in-msvc
  • 那个博客说在受影响的 CPU 上(我认为只有 Intel Skylake 系列),使用编译器选项使得性能与没有微码更新之前大致相同编译器选项。它没有说明它对其他 CPU 的影响,例如 Silvermont/Goldmont。 (我查看了 Agner Fog 的微架构指南,AMD Zen 对单个指令上的任意数量的前缀都没有问题,就像 Core2 以来的主流 Intel 一样。AMD Bulldozer 系列对解码超过 3 个前缀的指令有“非常大”的惩罚,比如 4-7 个前缀的 14-15 个周期。)

标签: assembly x86 intel cpu-architecture micro-optimization


【解决方案1】:

NOP 是一条单独的指令,必须单独解码并通过管道。正如What methods can be used to efficiently extend instruction length on modern x86? 中所讨论的那样,总是最好用前缀填充指令以实现所需的对齐,而不是插入 NOP(但仅限于不会导致某些无法处理的 CPU 出现重大停顿的方式大量的前缀)。

也许英特尔认为工具链在这种情况下这样做是值得的,因为这实际上是在内部循环内部,而不仅仅是内部循环外部的 NOP。 (并且在前一条指令上添加前缀相对简单。)


我现在有一些数据点。在 AMD FX 8300 上对 /QIntel-jcc-erratum 进行基准测试的结果是bad

对于特定基准测试,速度下降了十进制数量级,而英特尔 Skylake 在同一基准测试中的收益约为 20%。这与彼得的 cmets 一致:

我查看了 Agner Fog 的微架构指南,AMD Zen 对一条指令上的任意数量的前缀都没有问题,就像 Core2 以来的主流 Intel 一样。 AMD Bulldozer 系列对解码超过 3 个前缀的指令有“非常大”的惩罚,比如 4-7 个前缀需要 14-15 个周期

认为 Bulldozer 系列已经过时以至于不太关心它是有道理的,尽管肯定还有一些 APU 台式机和笔记本电脑,但它们肯定会在编译器放置 4 个或更多的循环中显示出很大的回归热内循环中一条指令的前缀(包括现有的前缀,如 REX 或 66h)。比 SKL 上 MITE 传统解码的 3% 差得多。

虽然确实 Bulldozer 系列已经过时,但我认为我无法承受这么大的影响。我也担心其他 CPU 可能会以同样的方式被额外的前缀阻塞。所以我的结论是/QIntel-jcc-erratum 用于一般目标软件。除非在特定的翻译单元中启用并动态分发到那里,否则大多数时候这太麻烦了。


在 MSVC 上可能安全的一件事是停止使用 /Os 标志。发现/Os标志至少:

  • 避免跳转表以支持条件跳转
  • 避免循环开始填充

试试下面的例子 (https://godbolt.org/z/jvezPd9jM):

void loop(int i, char a[], char b[])
{
    char* stop = a + i;
    while (a != stop){
        *b++ = *a++;
    }
}

void jump_table(int i, char a[], char b[])
{
    switch (i)
    {
                            case 7: 
            a[6] = b[6];    case 6: 
            a[5] = b[5];    case 5: 
            a[4] = b[4];    case 4: 
            a[3] = b[3];    case 3: 
            a[2] = b[2];    case 2: 
            a[1] = b[1];    case 1: 
            a[0] = b[1];    case 0:  break;
            default: __assume(false);
    }
}

这会导致更频繁地遇到 JCC 性能问题(避免跳转表会产生一系列 JCC,并且避免对齐会使小于 16b 的小循环有时也会触及边界)

【讨论】:

    猜你喜欢
    • 2020-07-30
    • 2020-09-30
    • 1970-01-01
    • 1970-01-01
    • 2017-05-08
    • 2016-08-27
    • 2019-09-09
    • 2022-01-19
    • 2013-10-30
    相关资源
    最近更新 更多