我对 Linux perf 进行了一些调查,以帮助在我的 Skylake i7-6700HQ 盒子上回答这个问题,Haswell 的结果已由另一位用户提供。以下分析适用于 Skylake,但随后是与 Haswell 的比较。
其他架构可能会有所不同0,为了帮助解决所有问题,我欢迎其他结果。 source is available)。
这个问题主要涉及前端,因为在最近的架构中,前端施加了每个周期四个融合域微指令的硬限制。
循环性能规则总结
首先,我将根据一些“性能规则”来总结结果,以便在处理小循环时牢记。还有很多其他的性能规则——这些是对它们的补充(即,你可能不会为了满足这些规则而违反另一个规则)。这些规则最直接适用于 Haswell 和更高版本的架构 - 请参阅 other answer 了解早期架构的差异概述。
首先,计算循环中 macro-fused 微指令的数量。您可以使用 Agner 的 instruction tables 直接查找每条指令,除了 ALU uop 和紧跟分支通常会融合到一个 uop 中。然后根据这个计数:
- 如果计数是 4 的倍数,则很好:这些循环以最佳方式执行。
- 如果计数是偶数且小于 32,则很好,除非是 10,在这种情况下,您应该尽可能展开到另一个偶数。
- 对于奇数,如果可以的话,您应该尝试展开到小于 32 或 4 的倍数的偶数。
- 对于大于 32 微指令但小于 64 的循环,如果它还不是 4 的倍数,您可能需要展开:使用超过 64 微指令,您将在 Sklyake 的任何值和几乎所有值下获得高效的性能在 Haswell 上(有一些偏差,可能与对齐有关)。这些循环的低效率仍然相对较小:最需要避免的值是
4N + 1 计数,其次是 4N + 2 计数。
调查结果摘要
对于由 uop 缓存提供的代码,没有明显的 4 倍数效应。任何数量的微指令的循环都可以以每个周期 4 个融合域微指令的吞吐量执行。
对于传统解码器处理的代码,情况正好相反:循环执行时间限制为整数个循环,因此不是 4 微指令的倍数的循环无法达到 4 微指令/循环,因为它们浪费了一些问题/执行槽。
对于从循环流检测器 (LSD) 发出的代码,情况是两者的混合,下面将更详细地解释。一般来说,小于 32 uop 且具有偶数 uop 的循环会以最佳方式执行,而奇数大小的循环则不会,而较大的循环需要 4 uop 的倍数才能以最佳方式执行。
英特尔怎么说
英特尔实际上在他们的优化手册中对此进行了说明,其他答案中的详细信息。
详情
正如任何精通最近的 x86-64 架构的人都知道的那样,在任何时候,前端的获取和解码部分都可能在几种不同的模式下工作,具体取决于代码大小和其他因素。事实证明,这些不同的模式在循环大小方面都有不同的行为。我将在后面单独介绍它们。
旧版解码器
旧版解码器1 是完整的机器码到 uops 解码器,当代码不适合时使用2在 uop 缓存机制(LSD 或 DSB)中。发生这种情况的主要原因是代码工作集大于 uop 缓存(在理想情况下大约为 ~1500 uop,实际上更少)。不过,对于这个测试,我们将利用如果对齐的 32 字节块包含超过 18 条指令3 也将使用传统解码器这一事实。
为了测试旧版解码器的行为,我们使用如下所示的循环:
short_nop:
mov rax, 100_000_000
ALIGN 32
.top:
dec rax
nop
...
jnz .top
ret
基本上,一个简单的循环倒计时直到rax 为零。所有指令都是单个 uop4 并且 nop 指令的数量是变化的(在显示为 ... 的位置)以测试不同大小的循环(因此 4-uop 循环将有 2 nops,加上两条循环控制指令)。没有宏观融合,因为我们总是将dec 和jnz 与至少一个nop 分开,也没有微观融合。最后,在(隐含的 icache 访问之外)没有内存访问。
请注意,这个循环非常密集 - 每条指令大约 1 个字节(因为 nop 指令每个有 1 个字节) - 所以我们将在 32B 块条件下触发 > 18 条指令只要在循环中达到 19 条指令。根据检查perf 性能计数器lsd.uops 和idq.mite_uops,这正是我们所看到的:基本上100% 的指令来自LSD5,直到并包括18 uop 循环,但在 19 uop 及以上,100% 来自传统解码器。
无论如何,以下是从 3 到 99 微秒的所有循环大小的循环/迭代6:
蓝点是适合 LSD 的循环,表现出一些复杂的行为。我们稍后会看这些。
红点(从 19 uops/迭代开始)由旧版解码器处理,并显示出非常可预测的模式:
- 所有带有
N uops 的循环都完全采用ceiling(N/4) 迭代
因此,至少对于旧版解码器而言,Peter 的观察完全适用于 Skylake:4 微指令的倍数的循环可能会在 IPC 为 4 时执行,但任何其他数量的微指令都会浪费 1 , 2 或 3 个执行槽(分别用于带有 4N+3、4N+2、4N+1 指令的循环)。
我不清楚为什么会发生这种情况。尽管如果您认为解码发生在连续的 16B 块中似乎很明显,因此在 4 微指令/周期循环的解码速率下,不是 4 的倍数在jnz 指令的循环中总是会有一些尾随(浪费)插槽遇到。然而,实际的获取和解码单元由预解码和解码阶段组成,中间有一个队列。预解码阶段实际上有 6 条指令的吞吐量,但每个周期只解码到 16 字节边界的末尾。这似乎意味着循环结束时出现的气泡可能会被预解码器 -> 解码队列吸收,因为预解码器的平均吞吐量高于 4。
因此,根据我对预解码器工作原理的理解,我无法完全解释这一点。可能是在解码或预解码中存在一些额外的限制,以防止非整数循环计数。例如,即使跳转后的指令在预解码队列中可用,传统解码器也可能无法解码跳转两侧的指令。或许和需要handlemacro-fusion有关吧。
上面的测试显示了循环顶部在 32 字节边界上对齐的行为。下面是同一张图,但添加了一个系列,显示循环顶部向上移动 2 个字节时的效果(即,现在在 32N + 30 边界处未对齐):
现在大多数循环大小都会受到 1 或 2 个循环的惩罚。当您考虑解码 16B 边界和每周期解码 4 条指令时,第 1 个惩罚案例是有意义的,并且 2 个周期惩罚案例发生在循环中,由于某种原因,DSB 用于循环中的 1 条指令(可能是 dec 指令它出现在它自己的 32 字节块中),并且会产生一些 DSBMITE 切换惩罚。
在某些情况下,当最终更好地对齐循环末端时,未对齐不会造成伤害。我测试了错位,它以相同的方式持续到 200 个 uop 循环。如果你从表面上看预解码器的描述,看起来,如上所述,它们应该能够隐藏一个未对齐的提取气泡,但它不会发生(可能队列不够大)。
DSB(Uop 缓存)
uop 缓存(英特尔喜欢将其称为 DSB)能够缓存大多数中等数量的指令循环。在典型的程序中,您希望大部分指令都在此缓存中提供7。
我们可以重复上面的测试,但现在从 uop 缓存中提供 uops。这是将我们的 nop 大小增加到 2 个字节的简单问题,因此我们不再达到 18 条指令的限制。我们在循环中使用 2 字节 nop xchg ax, ax:
long_nop_test:
mov rax, iters
ALIGN 32
.top:
dec eax
xchg ax, ax ; this is a 2-byte nop
...
xchg ax, ax
jnz .top
ret
在这里,结果非常简单。对于从 DSB 交付的所有测试循环大小,所需的循环数为 N/4 - 即,循环以最大理论吞吐量执行,即使它们没有 4 微指令的倍数。因此,一般而言,在 Skylake 上,由 DSB 提供的中等大小的循环无需担心确保 uop 计数满足某个特定倍数。
这是一个包含 1,000 个 uop 循环的图表。如果你眯着眼睛,你可以看到 64-uop 之前的次优行为(当循环在 LSD 中时)。在那之后,这是一个直击,4 IPC 一直到 1,000 微欧(有一个大约 900 的光点,这可能是由于我的盒子上的负载):
接下来,我们看看小到足以放入 uop 缓存的循环的性能。
LSD(循环蒸汽探测器)
重要提示:英特尔显然已禁用 Skylake(SKL150 勘误)和 Kaby Lake(KBL095、KBW095 勘误)芯片上的 LSD,通过微码更新和 Skylake- X 开箱即用,由于a bug 与超线程和 LSD 之间的交互有关。对于那些芯片,下图可能不会有高达 64 uop 的有趣区域;相反,它看起来与 64 微秒后的区域相同。
循环流检测器可以缓存高达 64 uop 的小循环(在 Skylake 上)。在英特尔最近的文档中,它更多地被定位为一种节能机制而不是一种性能特性——尽管使用 LSD 肯定没有提到性能方面的缺点。
针对应该适合 LSD 的循环大小运行此程序,我们得到以下循环/迭代行为:
这里的红线是从 LSD 交付的微指令的百分比。对于从 5 到 56 uop 的所有循环大小,它都以 100% 的速度保持平坦。
对于 3 和 4 uop 循环,我们有不同寻常的行为,即分别有 16% 和 25% 的 uop 来自传统解码器。嗯?幸运的是,它似乎并没有影响循环吞吐量,因为这两种情况都达到了 1 个循环/循环的最大吞吐量——尽管人们可以预期一些 MITELSD 转换惩罚。
在 57 和 62 uop 的循环大小之间,从 LSD 传递的 uop 数量表现出一些奇怪的行为 - 大约 70% 的 uop 是从 LSD 传递的,其余的则从 DSB 传递。 Skylake 名义上具有 64-uop LSD,所以这是在超过 LSD 大小之前的某种过渡——也许在 IDQ(实现 LSD)内存在某种内部对齐,仅导致部分命中这个阶段的LSD。这个阶段很短,而且在性能方面,似乎主要是之前的完全 LSD 性能和之后的完全 DSB 性能的线性组合。
让我们看一下 5 到 56 uop 之间的主要结果。我们看到三个不同的区域:
从 3 到 10 微秒的循环:这里的行为很复杂。这是唯一一个我们看到循环计数无法用单次循环迭代的静态行为解释的区域8。范围足够短,很难说是否有模式。 4、6 和 8 微指令的循环都以N/4 个循环以最佳方式执行(与下一个区域的模式相同)。
另一方面,10 微指令的循环在每次迭代中执行 2.66 个循环,使其成为唯一一个在达到 34 微指令或更高的循环大小(异常值除外)之前不会以最佳方式执行的循环大小26)。这对应于类似于4, 4, 4, 3 的重复 uop/循环执行率。对于 5 uop 的循环,每次迭代可以获得 1.33 个循环,非常接近但与理想的 1.25 不同。这对应于4, 4, 4, 4, 3 的执行率。
这些结果很难解释。结果在每次运行中都是可重复的,并且对于诸如将 nop 替换为实际上执行类似mov ecx, 123 的指令之类的更改具有鲁棒性。这可能与每 2 个周期 1 个分支的限制有关,这适用于所有循环,除了那些“非常小”的循环。可能是微指令偶尔排成一列,导致这种限制出现,导致额外的循环。一旦达到 12 uop 或更高,这永远不会发生,因为每次迭代总是至少需要三个周期。
从 11 微秒到 32 微秒的循环:我们看到一个阶梯模式,但周期为 2。基本上,所有具有 偶数 个 uops 的循环都以最佳方式执行 - 即,恰好采用 N/4 个循环。具有奇数个微指令的循环会浪费一个“问题槽”,并且与具有更多微指令的循环占用相同的周期数(即,一个 17 微指令的循环与一个 18 微指令的循环占用相同的 4.5 个循环)。因此,在许多 uop 计数方面,我们的行为优于 ceiling(N/4),并且我们有第一个证据表明 Skylake 至少可以在非整数周期中执行循环。
唯一的异常值是 N=25 和 N=26,这两个值都比预期的要长约 1.5%。它很小但可重现,并且可以在文件中移动函数。这太小了,无法用每次迭代效应来解释,除非它有一个巨大的周期,所以它可能是别的东西。
这里的整体行为与硬件展开循环完全一致(除了 25/26 异常)2 倍。
从 33 到 ~64 uop 的循环:我们再次看到阶梯模式,但周期为 4,平均性能比高达 32 uop 的情况更差。行为正是ceiling(N/4) - 也就是说,与传统解码器情况相同。因此,对于 32 到 64 uop 的循环,LSD 并没有比传统解码器提供明显的优势,就这一特殊限制的前端吞吐量而言。当然,LSD 还有许多其他更好的方法 - 它避免了许多潜在的解码瓶颈,这些瓶颈会出现在更复杂或更长的指令中,并且可以节省功率等。
所有这一切都非常令人惊讶,因为这意味着从 uop 缓存传递的循环通常在前端比从 LSD 传递的循环执行更好,尽管 LSD 通常被定位为严格比 DSB 更好的 uops 来源(例如,作为建议的一部分,尝试保持循环足够小以适合 LSD)。
这是查看相同数据的另一种方法 - 就给定微指令计数的效率损失而言,与每个周期 4 微指令的理论最大吞吐量相比。 10% 的效率命中意味着您只有通过简单的N/4 公式计算的吞吐量的 90%。
这里的整体行为与硬件不进行任何展开是一致的,这是有道理的,因为超过 32 uop 的循环根本无法在 64 uop 的缓冲区中展开。
上面讨论的三个区域颜色不同,至少可以看到竞争效果:
在其他条件相同的情况下,涉及的微指令数量越多,效率影响就越低。命中是每次迭代只有一次的固定成本,因此较大的循环支付较小的相对成本。
当您跨入 33+ uop 区域时,效率会大幅提高:吞吐量损失的大小都会增加,并且受影响的 uop 计数会增加一倍。
第一个区域有些混乱,7 uop 是最差的总体 uop 计数。
对齐
上面的 DSB 和 LSD 分析是针对与 32 字节边界对齐的循环条目,但未对齐的情况似乎在这两种情况下都没有受到影响:与对齐的情况没有实质性差异(除了可能不到 10 微秒的一些小变化,我没有进一步调查)。
以下是 32N-2 和 32N+2 的未对齐结果(即循环顶部 2 个字节之前和之后的 32B 边界):
还显示了理想的N/4 行以供参考。
哈斯韦尔
接下来接下来看看之前的微架构:Haswell。这里的号码由用户Iwillnotexist Idonotexist慷慨提供。
LSD + 传统解码管道
首先,来自“密集代码”测试的结果,该测试测试 LSD(对于较小的 uop 计数)和旧式管道(对于较大的 uop 计数,因为指令密度导致循环“退出”DSB。
我们马上就看到了差异,就何时而言,每个架构从 LSD 为密集循环提供微指令。下面我们比较 Skylake 和 Haswell 的 密集 代码短循环(每条指令 1 个字节)。
如上所述,Skylake 循环在 19 uop 处停止从 LSD 传递,正如代码限制的每 32 字节区域 18 uop 所预期的那样。另一方面,Haswell 似乎也停止从 LSD 可靠地为 16-uop 和 17-uop 循环提供数据。我对此没有任何解释。 3 uop 情况下也有区别:奇怪的是,在 3 和 4 uop 情况下,两个处理器都只从 LSD 中提供 一些 uop,但 4 uop 的确切数量是相同的uops,与 3 不同。
不过,我们主要关心的是实际性能,对吧?因此,让我们看看 32 字节对齐的 dense 代码案例的循环/迭代:
这与上面显示的 Skylake 数据相同(未对齐的系列已被删除),Haswell 绘制在旁边。您会立即注意到 Haswell 的模式相似,但不一样。如上,这里有两个区域:
旧版解码
大于 ~16-18 uop(不确定性如上所述)的循环是从旧版解码器提供的。 Haswell 的模式与 Skylake 有所不同。
对于 19-30 uop 的范围,它们是相同的,但在此之后 Haswell 打破了这种模式。 Skylake 使用了 ceil(N/4) 个周期来处理从传统解码器提供的循环。另一方面,Haswell 似乎采用了ceil((N+1)/4) + ceil((N+2)/12) - ceil((N+1)/12) 之类的东西。好的,这很混乱(更短的形式,有人吗?) - 但基本上这意味着虽然 Skylake 以 4*N 循环最佳地执行循环(即,以 4-uops/循环),但这样的循环(本地)通常是 最少 最佳计数(至少在本地) - 执行此类循环比 Skylake 多一个周期。所以实际上你最好在 Haswell 上使用 4N-1 uops 的循环,除了 25% 的这种循环 也是 的形式为 16-1N (31, 47、63 等)需要一个额外的周期。它开始听起来像闰年计算 - 但这种模式可能最好在上面直观地理解。
我不认为这种模式是 内在 uop 在 Haswell 上调度的,所以我们不应该过多地阅读它。好像是用
解释的
0000000000455a80 <short_nop_aligned35.top>:
16B cycle
1 1 455a80: ff c8 dec eax
1 1 455a82: 90 nop
1 1 455a83: 90 nop
1 1 455a84: 90 nop
1 2 455a85: 90 nop
1 2 455a86: 90 nop
1 2 455a87: 90 nop
1 2 455a88: 90 nop
1 3 455a89: 90 nop
1 3 455a8a: 90 nop
1 3 455a8b: 90 nop
1 3 455a8c: 90 nop
1 4 455a8d: 90 nop
1 4 455a8e: 90 nop
1 4 455a8f: 90 nop
2 5 455a90: 90 nop
2 5 455a91: 90 nop
2 5 455a92: 90 nop
2 5 455a93: 90 nop
2 6 455a94: 90 nop
2 6 455a95: 90 nop
2 6 455a96: 90 nop
2 6 455a97: 90 nop
2 7 455a98: 90 nop
2 7 455a99: 90 nop
2 7 455a9a: 90 nop
2 7 455a9b: 90 nop
2 8 455a9c: 90 nop
2 8 455a9d: 90 nop
2 8 455a9e: 90 nop
2 8 455a9f: 90 nop
3 9 455aa0: 90 nop
3 9 455aa1: 90 nop
3 9 455aa2: 90 nop
3 9 455aa3: 75 db jne 455a80 <short_nop_aligned35.top>
在这里,我注意到每条指令出现在 16B 解码块 (1-3) 中,以及解码它的周期。规则基本上是最多接下来的 4 条指令被解码,只要它们落在当前的 16B 块中。否则他们必须等到下一个周期。对于 N=35,我们看到在周期 4 中丢失了 1 个解码槽(在 16B 块中只剩下 3 条指令),但否则循环与 16B 边界甚至最后一个周期( 9)可以解码4条指令。
这里是 N=36 的截断图,除了循环结束之外,它是相同的:
0000000000455b20 <short_nop_aligned36.top>:
16B cycle
1 1 455a80: ff c8 dec eax
1 1 455b20: ff c8 dec eax
1 1 455b22: 90 nop
... [29 lines omitted] ...
2 8 455b3f: 90 nop
3 9 455b40: 90 nop
3 9 455b41: 90 nop
3 9 455b42: 90 nop
3 9 455b43: 90 nop
3 10 455b44: 75 da jne 455b20 <short_nop_aligned36.top>
现在在第三个也是最后一个 16B 块中有 5 条指令要解码,因此需要一个额外的周期。基本上 35 条指令,对于这种特定的指令模式恰好与 16B 位边界更好地对齐,并且在解码时节省了一个周期。这并不意味着 N=35 通常比 N=36 好!不同的指令将具有不同的字节数,并且会以不同的方式排列。一个类似的对齐问题也解释了每 16 个字节所需的额外周期:
16B cycle
...
2 7 45581b: 90 nop
2 8 45581c: 90 nop
2 8 45581d: 90 nop
2 8 45581e: 90 nop
3 8 45581f: 75 df jne 455800 <short_nop_aligned31.top>
这里最后的jne 已滑入下一个 16B 块(如果指令跨越 16B 边界,它实际上是在后一个块中),导致额外的循环丢失。这仅每 16 个字节发生一次。
因此,Haswell 旧解码器的结果可以通过旧解码器完美解释,其行为如 Agner Fog 的microarchitecture doc 中所述。事实上,如果您假设 Skylake 每个周期可以解码 5 条指令(最多提供 5 条微指令)9,它似乎也可以解释 Skylake 的结果。假设它可以,Skylake 的渐近传统解码吞吐量此代码仍然是 4-uops,因为 16 个 nops 块解码 5-5-5-1,而不是 4-4-4-4在 Haswell 上,因此您只能在边缘获得好处:例如,在上面 N=36 的情况下,Skylake 可以解码所有剩余的 5 条指令,而 Haswell 为 4-1,节省了一个周期。
结果是,似乎可以以一种相当简单的方式理解传统解码器的行为,主要的优化建议是继续按摩代码,使其“聪明地”落入 16B 对齐的块中(也许这就像垃圾箱一样是 NP 难的?)。
DSB(又是 LSD)
接下来让我们看一下代码从 LSD 或 DSB 提供的场景 - 通过使用“long nop”测试避免打破每 32B 块 18-uop 的限制,因此保留在 DSB 中。
哈斯维尔 vs Skylake:
注意 LSD 的行为 - 这里 Haswell 在 57 微秒处停止使用 LSD,这与发布的 57 微秒的 LSD 大小完全一致。没有像我们在 Skylake 上看到的那样奇怪的“过渡期”。 Haswell 对于 3 和 4 微指令也有奇怪的行为,其中只有 ~0% 和 ~40% 的微指令分别来自 LSD。
在性能方面,Haswell 通常与 Skylake 保持一致,但有一些偏差,例如,大约 65、77 和 97 微秒,它向上舍入到下一个周期,而 Skylake 始终能够维持 4 微秒/周期甚至当这导致非整数周期数时。 25 和 26 微秒与预期的轻微偏差已经消失。也许 Skylake 的 6 uop 交付率有助于避免 Haswell 因其 4 uop 交付率而遭受的 uop-cache 对齐问题。
其他架构
以下附加架构的结果由用户 Andreas Abel 友情提供,但由于此处的字符数限制,我们将不得不使用另一个答案进行进一步分析。
需要帮助
尽管社区提供了许多平台的结果,但我仍然对比 Nehalem 更老、比 Coffee Lake 更新的芯片(特别是 Cannon Lake,它是一个新的 uarch)的结果感兴趣。生成这些结果的代码is public。此外,are available 上面的结果也在 GitHub 中以 .ods 格式显示。
0 特别是,传统解码器的最大吞吐量在 Skylake 中明显从 4 uop 增加到 5 uop,uop 缓存的最大吞吐量从 4 增加到 6。这两者都可能影响结果描述here。
1 英特尔实际上喜欢将遗留解码器称为 MITE(微指令翻译引擎),可能是因为使用 legacy 实际标记架构的任何部分是一种错误 内涵。
2 从技术上讲,还有另一个更慢的 uops 来源 - MS(微码排序引擎),用于实现任何超过 4 uops 的指令,但我们在这里忽略它,因为我们的循环都不包含微编码指令。
3 这是因为任何对齐的 32 字节块在其 uop 缓存槽中最多可以使用 3 路,并且每个槽最多可容纳 6 个 uop。所以如果你在一个 32B 的块中使用超过 3 * 6 = 18 uops,代码根本无法存储在 uop 缓存中。在实践中可能很少会遇到这种情况,因为代码需要非常密集(每条指令少于 2 个字节)才能触发。
4nop 指令解码为一个 uop,但在执行之前不会被消除(即它们不使用执行端口) - 但仍会占用前端,因此要计入我们感兴趣的各种限制。
5 LSD 是循环流检测器,它直接在 IDQ 中缓存最多 64 (Skylake) uop 的小循环。在早期的架构中,它可以容纳 28 微指令(两个逻辑核心都处于活动状态)或 56 微指令(一个逻辑核心处于活动状态)。
6 我们不能轻易地在这种模式中适应 2 uop 循环,因为这意味着零个 nop 指令,这意味着 dec 和 jnz 指令将宏融合,随着 uop 计数的相应变化。相信我的话,所有具有 4 个或更少微指令的循环最多只能执行 1 个循环/迭代。
7 为了好玩,我只是在一小段 Firefox 上运行 perf stat,我打开一个标签并点击了几个 Stack Overflow 问题。对于交付的指令,我从 DSB 获得 46%,从传统解码器获得 50%,从 LSD 获得 4%。这表明,至少对于像浏览器这样的大而复杂的代码,DSB 仍然无法捕获大部分代码(幸运的是,遗留解码器还不错)。
8 我的意思是,所有其他循环计数都可以通过简单地通过在 uops 中采用“有效”积分循环成本来解释(这可能高于实际大小是 uops)并除以 4。对于这些非常短的循环,这是行不通的 - 通过将任何整数除以 4,您无法达到每次迭代 1.333 个周期。换句话说,在所有其他区域,成本的形式为 N/4对于某个整数 N。
9 事实上,我们知道 Skylake 可以从传统解码器每个周期提供 5 微指令,但我们不知道这 5 微指令是否可以来自 5 个不同的说明,或者只有 4 个或更少。也就是说,我们希望 Skylake 可以在模式2-1-1-1 中解码,但我不确定它是否可以在模式1-1-1-1-1 中解码。以上结果提供了一些证据,证明它确实可以解码1-1-1-1-1。