【问题标题】:Can the simple decoders in recent Intel microarchitectures handle all 1-µop instructions?最近的英特尔微架构中的简单解码器可以处理所有 1-µop 指令吗?
【发布时间】:2020-09-10 18:39:28
【问题描述】:

最新的 Intel CPU 的前端包含一个复杂的解码器和许多简单的解码器。复杂解码器可以处理解码为多个 µop 的指令,而简单解码器仅支持解码为单个(融合域)µop 的指令。

所有 1-µop 指令都可以由简单解码器解码,还是有 1-µop 指令只能由复杂解码器处理?

【问题讨论】:

  • 我想我可能读过一些关于在简单解码器中无法解码的指令,但我不认为它适用于 SnB 系列 CPU;也许是一个低功耗的uarch。 (英特尔解码器将宏可熔指令保留到下一组,以防有 jcc,但我不是那个意思)。是否有任何提示/证据表明简单的解码器可能无法处理我们可以进一步调查的每一个单微指令?
  • "xor rax, rax; setnle al" 如果通过解码器,则吞吐量为 1;如果它来自 DSB,则吞吐量如预期的那样为 0.5 个周期。这似乎表明 setnle 可能只能使用复杂的解码器。还是在我遗漏的第一种情况下还有其他瓶颈?
  • 有趣; xor eax,eax 是否按预期运行?当不是来自 DSB 时,使用虚拟 REP 或 DS 而不是 REX.W 前缀填充它是否仍会减慢速度?
  • xor eax, eax; setnle alxor rax, rax; setnle al 具有相同的行为。
  • 另外,如果我添加另一个需要复杂解码器的指令,例如 xor rbx, rbx; setnle bl; movq2dq xmm0, mm0,吞吐量变为 2(在 DSB 情况下为 1)。

标签: x86 x86-64 cpu intel cpu-architecture


【解决方案1】:

不,有些指令只能解码 1/clock

Andreas 的 cmets 表明 xor eax,eax / setnle al 似乎有 1/clock 的解码瓶颈。我在cdq 上发现了同样的情况:读取 EAX,写入 EDX,从 DSB(uop 缓存)中运行也明显更快,并且不涉及部分寄存器或任何奇怪的东西,并且不需要 dep-破坏指令。

更好的是,作为单字节指令,它只需一小段指令就可以击败 DSB。 (导致在某些 CPU 上的测试结果产生误导,例如在 Agner Fog 的表和 https://uops.info/ 上,例如 SKX 显示为 1c 吞吐量。)https://www.uops.info/html-tp/SKX/CDQ-Measurements.htmlhttps://www.uops.info/html-tp/CFL/CDQ-Measurements.html 的吞吐量不一致,因为测试方法不同:只有 Coffee Lake 测试曾使用足够小的展开计数 (10) 进行测试,不会破坏 DSB,发现吞吐量为 0.6。 (考虑到循环开销后,实际吞吐量为 0.5,完全可以通过与 cqo 相同的后端端口压力来解释。IDK 为什么您会发现 0.6 而不是 0.55,而循环中 p6 只有一个额外的 uop。)

(Zen 可以以 0.25c 的吞吐量运行这条指令;没有奇怪的解码问题,并且由每个整数 ALU 端口处理。)


dec/jnz 循环中的times 10 cdq 可以从 uop 缓存运行,并在 Skylake (p06) 上以 0.5c 的吞吐量运行,加上循环开销也会竞争 p6。

times 20 cdq 对于一个 32 字节的机器代码块而言,超过 3 个 uop 缓存行,这意味着循环只能从传统解码运行(循环顶部对齐)。在 Skylake 上,每个 cdq 运行 1 个周期。 Perf 计数器确认 MITE 每个周期提供 1 uop,而不是 3 或 4 组,中间有空闲周期。

default rel
%ifdef __YASM_VER__
    CPU Skylake AMD
%else
%use smartalign
alignmode p6, 64
%endif

global _start
_start:
    mov  ebp, 1000000000

align 64
.loop:
    ;times 10 cdq   ; 0.5c throughput
    ;times 20 cdq   ; 1c throughput, 1 MITE uop per cycle front-end

    ; times 10 cqo        ; 0.5c throughput 2-byte insn fits uop cache
    ; times 10 cdqe       ; 1c throughput data dependency
    ;times 10 cld         ; ~4c throughput, 3 uops

    dec ebp
    jnz .loop
.end:

    xor edi,edi
    mov eax,231   ; __NR_exit_group  from /usr/include/asm/unistd_64.h
    syscall       ; sys_exit_group(0)

在我的 Arch Linux 桌面上,我将它构建成一个静态可执行文件以在 perf 下运行:

  • i7-6700k 与 epp=balance_performance(最大“turbo”= 3.9GHz)
  • 微码修订版 0xd6(因此禁用了 LSD,这并不重要:如果循环的所有微指令都在 DSB 微指令缓存 IIRC 中,则循环只能从 LSD 循环缓冲区运行。)
     in a bash shell:
t=cdq-latency; nasm -f elf64 "$t".asm && ld -o "$t" "$t.o" && objdump -drwC -Mintel "$t" && taskset -c 3 perf stat --all-user -etask-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,uops_issued.any,frontend_retired.dsb_miss,idq.dsb_uops,idq.mite_uops,idq.mite_cycles,idq_uops_not_delivered.core,idq_uops_not_delivered.cycles_fe_was_ok,idq.all_mite_cycles_4_uops ./"$t"

反汇编

0000000000401000 <_start>:
  401000:       bd 00 ca 9a 3b          mov    ebp,0x3b9aca00
  401005:       0f 1f 84 00 00 00 00 00         nop    DWORD PTR [rax+rax*1+0x0]
...
  40103d:       0f 1f 00                nop    DWORD PTR [rax]

0000000000401040 <_start.loop>:
  401040:       99                      cdq    
  401041:       99                      cdq    
  401042:       99                      cdq    
  401043:       99                      cdq    
...
  401052:       99                      cdq    
  401053:       99                      cdq             # 20 total CDQ
  401054:       ff cd                   dec    ebp
  401056:       75 e8                   jne    401040 <_start.loop>

0000000000401058 <_start.end>:
  401058:       31 ff                   xor    edi,edi
  40105a:       b8 e7 00 00 00          mov    eax,0xe7
  40105f:       0f 05                   syscall 

性能结果:

 Performance counter stats for './cdq-latency':

          5,205.44 msec task-clock                #    1.000 CPUs utilized          
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 1      page-faults               #    0.000 K/sec                  
    20,124,711,776      cycles                    #    3.866 GHz                      (49.88%)
    22,015,118,295      instructions              #    1.09  insn per cycle           (59.91%)
    21,004,212,389      uops_issued.any           # 4035.049 M/sec                    (59.97%)
     1,005,872,141      frontend_retired.dsb_miss #  193.235 M/sec                    (60.03%)
                 0      idq.dsb_uops              #    0.000 K/sec                    (60.08%)
    20,997,157,414      idq.mite_uops             # 4033.694 M/sec                    (60.12%)
    19,996,447,738      idq.mite_cycles           # 3841.451 M/sec                    (40.03%)
    59,048,559,790      idq_uops_not_delivered.core # 11343.621 M/sec                   (39.97%)
       112,956,733      idq_uops_not_delivered.cycles_fe_was_ok #   21.700 M/sec                    (39.92%)
           209,490      idq.all_mite_cycles_4_uops #    0.040 M/sec                    (39.88%)

       5.206491348 seconds time elapsed

所以循环开销(dec/jnz)基本上是免费发生的,在与最后一个cdq 相同的循环中解码。计数不准确,因为我在一次运行中使用了太多事件(启用了 HT),所以 perf 进行了软件多路复用。从另一个计数器更少的运行中:

# same source, only these HW counters enabled to avoid multiplexing
          5,161.14 msec task-clock                #    1.000 CPUs utilized          

    20,107,065,550      cycles                    #    3.896 GHz                    
    20,000,134,955      idq.mite_cycles           # 3875.142 M/sec                  
    59,050,860,720      idq_uops_not_delivered.core # 11441.447 M/sec                 
        95,968,317      idq_uops_not_delivered.cycles_fe_was_ok #   18.594 M/sec                  

所以我们可以看到 MITE(传统解码)基本上每个周期都处于活动状态,并且前端基本上从来没有“ok”。 (即永远不会在后端停滞不前)。


只有 10 个 CDQ 指令,让 DSB 工作

...
0000000000401040 <_start.loop>:
  401040:       99                      cdq    
  401041:       99                      cdq    
...
  401049:       99                      cdq        # 10 total CDQ insns
  40104a:       ff cd                   dec    ebp
  40104c:       75 f2                   jne    401040 <_start.loop>

 Performance counter stats for './cdq-latency' (4 runs):

          1,417.38 msec task-clock                #    1.000 CPUs utilized            ( +-  0.03% )
                 0      context-switches          #    0.000 K/sec                  
                 0      cpu-migrations            #    0.000 K/sec                  
                 1      page-faults               #    0.001 K/sec                  
     5,511,283,047      cycles                    #    3.888 GHz                      ( +-  0.03% )  (49.83%)
    11,997,247,694      instructions              #    2.18  insn per cycle           ( +-  0.00% )  (59.99%)
    10,999,182,841      uops_issued.any           # 7760.224 M/sec                    ( +-  0.00% )  (60.17%)
           197,753      frontend_retired.dsb_miss #    0.140 M/sec                    ( +- 13.62% )  (60.21%)
    10,988,958,908      idq.dsb_uops              # 7753.010 M/sec                    ( +-  0.03% )  (60.21%)
        10,234,859      idq.mite_uops             #    7.221 M/sec                    ( +- 27.43% )  (60.21%)
         8,114,909      idq.mite_cycles           #    5.725 M/sec                    ( +- 26.11% )  (39.83%)
        40,588,332      idq_uops_not_delivered.core #   28.636 M/sec                    ( +- 21.83% )  (39.79%)
     5,502,581,002      idq_uops_not_delivered.cycles_fe_was_ok # 3882.221 M/sec                    ( +-  0.01% )  (39.79%)
            56,223      idq.all_mite_cycles_4_uops #    0.040 M/sec                    ( +-  3.32% )  (39.79%)

          1.417599 +- 0.000489 seconds time elapsed  ( +-  0.03% )

正如idq_uops_not_delivered.cycles_fe_was_ok所报告的,基本上所有未使用的前端uop插槽都是后端的故障(p0 / p6上的端口压力),而不是前端。

【讨论】:

  • 非常有趣。我想知道这些说明是否有某种模式,例如也许它们看起来(在操作码或其他方面)与需要多个微指令的指令相似?据推测,问题在于将这些引导到复杂解码器的引导逻辑中的启发式算法。另一种解释是,它们确实必须使用复杂的解码器,因为它们有一些更复杂的东西,但这似乎不太可能。
  • @BeeOnRope:保持转向逻辑简单(和低延迟?)听起来是个不错的猜测。这比通过不复制解码cdq 的逻辑来保持简单解码器更简单更有意义。 setcc 它的作用相对奇怪(只读取标志,写入寄存器,虽然它实际上是 RMW 寄存器,因为英特尔不再重命名低 8 寄存器),但我认为那只是用于后端;在前端,它是一个普通的 2 字节操作码 + modrm。
  • @BeeOnRope:如果您想进一步调查,这里列出了似乎需要 Skylake 复杂解码器的 1-uop 指令列表:justpaste.it/85otd,这是 Haswell 的一个:@ 987654326@
  • @AndreasAbel:YMM-destination VPMOVZX/SX* 在列表中的存在让我想到它根本无法微融合内存操作数,即使它不是索引的寻址方式。 XMM 版本可以,但 YMM 版本不能。但是使用寄存器源它只有 1 uop。至于bswap r32,同样的操作码是 2 uop,操作数大小为 64 位。 bt* 的内存目标可能很奇怪,所以这是有道理的。是的,非常有趣,对于某些组这样的指令可能会有一些合理的解释。
  • 对于setcccmovcc,这种行为可以通过以下事实来解释:指令的一些变体需要两个微指令(像cmovbe这样的指令从两个微指令中读取) SPAZOC 标志组)。预解码器仅根据操作码进行引导,然后解码器整理出需要多少个微指令? VPMOVSX* 也一样,因为在这种情况下缺乏融合。
猜你喜欢
  • 2015-06-29
  • 2015-03-26
  • 2016-08-30
  • 2020-08-23
  • 2011-10-15
  • 1970-01-01
  • 1970-01-01
  • 2020-05-12
  • 1970-01-01
相关资源
最近更新 更多