2

tl;dr:我有两个功能等效的 C 代码,我用 Clang 编译(事实上它是 C 代码并不重要;我认为只有程序集很有趣),IACA 告诉我应该更快,但我不明白为什么,我的基准测试显示两个代码的性能相同。

我有以下 C 代码(暂时忽略#include "iacaMarks.h", IACA_START, IACA_END):

参考c:

#include "iacaMarks.h"
#include <x86intrin.h>

#define AND(a,b)  _mm_and_si128(a,b)
#define OR(a,b)   _mm_or_si128(a,b)
#define XOR(a,b)  _mm_xor_si128(a,b)
#define NOT(a)    _mm_andnot_si128(a,_mm_set1_epi32(-1))

void sbox_ref (__m128i r0,__m128i r1,__m128i r2,__m128i r3,
               __m128i* r5,__m128i* r6,__m128i* r7,__m128i* r8) {
  __m128i r4;

  IACA_START
  r3 = XOR(r3,r0);
  r4 = r1;
  r1 = AND(r1,r3);
  r4 = XOR(r4,r2);
  r1 = XOR(r1,r0);
  r0 = OR(r0,r3);
  r0 = XOR(r0,r4);
  r4 = XOR(r4,r3);
  r3 = XOR(r3,r2);
  r2 = OR(r2,r1);
  r2 = XOR(r2,r4);
  r4 = NOT(r4);
  r4 = OR(r4,r1);
  r1 = XOR(r1,r3);
  r1 = XOR(r1,r4);
  r3 = OR(r3,r0);
  r1 = XOR(r1,r3);
  r4 = XOR(r4,r3);
  *r5 = r1;
  *r6 = r4;
  *r7 = r2;
  *r8 = r0;
  IACA_END

}

我想知道是否可以通过手动重新调度一些指令来优化它(我很清楚 C 编译器应该产生有效的调度,但我的实验表明并非总是如此)。在某些时候,我尝试了以下代码(它与上面的代码相同,只是没有使用临时变量来存储稍后分配给*r5and的 XOR 的结果*r6):

重新调度.c:

#include "iacaMarks.h"
#include <x86intrin.h>

#define AND(a,b)  _mm_and_si128(a,b)
#define OR(a,b)   _mm_or_si128(a,b)
#define XOR(a,b)  _mm_xor_si128(a,b)
#define NOT(a)    _mm_andnot_si128(a,_mm_set1_epi32(-1))

void sbox_resched (__m128i r0,__m128i r1,__m128i r2,__m128i r3,
                   __m128i* r5,__m128i* r6,__m128i* r7,__m128i* r8) {
  __m128i r4;

  IACA_START
  r3 = XOR(r3,r0);
  r4 = r1;
  r1 = AND(r1,r3);
  r4 = XOR(r4,r2);
  r1 = XOR(r1,r0);
  r0 = OR(r0,r3);
  r0 = XOR(r0,r4);
  r4 = XOR(r4,r3);
  r3 = XOR(r3,r2);
  r2 = OR(r2,r1);
  r2 = XOR(r2,r4);
  r4 = NOT(r4);
  r4 = OR(r4,r1);
  r1 = XOR(r1,r3);
  r1 = XOR(r1,r4);
  r3 = OR(r3,r0);
  *r7 = r2;
  *r8 = r0;
  *r5 = XOR(r1,r3); // This two lines are different
  *r6 = XOR(r4,r3); // (no more temporary variables)
  IACA_END
}

我正在使用针对我的 i5-6500 (Skylake) 的 Clang 5.0.0 编译这些代码,并带有标志-O3 -march=native(我省略了生成的汇编代码,因为它们可以在下面的 IACA 输出中找到,但如果你愿意要直接将它们放在这里,问我,我会添加它们)。我对这两个代码进行了基准测试,并没有发现它们之间的任何性能差异。出于好奇,我对它们运行了 IACA,我惊讶地发现它说第一个版本需要 6 个周期才能运行,而第二个版本需要 7 个周期。以下是 IACA 的产出:

对于第一个版本:

dada@dada-ubuntu ~/perf % clang -O3 -march=native -c ref.c && ./iaca -arch SKL ref.o        
Intel(R) Architecture Code Analyzer Version -  v3.0-28-g1ba2cbb build date: 2017-10-23;16:42:45
Analyzed File -  ref_iaca.o
Binary Format - 64Bit
Architecture  -  SKL
Analysis Type - Throughput

Throughput Analysis Report
--------------------------
Block Throughput: 6.00 Cycles       Throughput Bottleneck: FrontEnd
Loop Count:  23
Port Binding In Cycles Per Iteration:
--------------------------------------------------------------------------------------------------
|  Port  |   0   -  DV   |   1   |   2   -  D    |   3   -  D    |   4   |   5   |   6   |   7   |
--------------------------------------------------------------------------------------------------
| Cycles |  6.0     0.0  |  6.0  |  1.3     0.0  |  1.4     0.0  |  4.0  |  6.0  |  0.0  |  1.4  |
--------------------------------------------------------------------------------------------------

DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3)
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion occurred
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256/AVX512 instruction, dozens of cycles penalty is expected
X - instruction not supported, was not accounted in Analysis

| Num Of   |                    Ports pressure in cycles                         |      |
|  Uops    |  0  - DV    |  1   |  2  -  D    |  3  -  D    |  4   |  5   |  6   |  7   |
-----------------------------------------------------------------------------------------
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm4, xmm3, xmm0
|   1      |             | 1.0  |             |             |      |      |      |      | vpand xmm5, xmm4, xmm1
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm2, xmm1
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm5, xmm5, xmm0
|   1      |             | 1.0  |             |             |      |      |      |      | vpor xmm0, xmm3, xmm0
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm0, xmm0, xmm1
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm1, xmm4, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm3, xmm4, xmm2
|   1      |             |      |             |             |      | 1.0  |      |      | vpor xmm2, xmm5, xmm2
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm2, xmm2, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpcmpeqd xmm4, xmm4, xmm4
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm1, xmm4
|   1      | 1.0         |      |             |             |      |      |      |      | vpor xmm1, xmm5, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm4, xmm5, xmm3
|   1      |             |      |             |             |      | 1.0  |      |      | vpor xmm3, xmm0, xmm3
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm4, xmm4, xmm3
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm4, xmm4, xmm1
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm1, xmm3
|   2^     |             |      | 0.3         | 0.3         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rdi], xmm4
|   2^     |             |      | 0.3         | 0.3         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rsi], xmm1
|   2^     |             |      | 0.3         | 0.3         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rdx], xmm2
|   2^     |             |      | 0.3         | 0.3         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rcx], xmm0
Total Num Of Uops: 26

对于第二个版本:

dada@dada-ubuntu ~/perf % clang -O3 -march=native -c resched.c && ./iaca -arch SKL resched.o
Intel(R) Architecture Code Analyzer Version -  v3.0-28-g1ba2cbb build date: 2017-10-23;16:42:45
Analyzed File -  resched_iaca.o
Binary Format - 64Bit
Architecture  -  SKL
Analysis Type - Throughput

Throughput Analysis Report
--------------------------
Block Throughput: 7.00 Cycles       Throughput Bottleneck: Backend
Loop Count:  22
Port Binding In Cycles Per Iteration:
--------------------------------------------------------------------------------------------------
|  Port  |   0   -  DV   |   1   |   2   -  D    |   3   -  D    |   4   |   5   |   6   |   7   |
--------------------------------------------------------------------------------------------------
| Cycles |  6.0     0.0  |  6.0  |  1.3     0.0  |  1.4     0.0  |  4.0  |  6.0  |  0.0  |  1.3  |
--------------------------------------------------------------------------------------------------

DV - Divider pipe (on port 0)
D - Data fetch pipe (on ports 2 and 3)
F - Macro Fusion with the previous instruction occurred
* - instruction micro-ops not bound to a port
^ - Micro Fusion occurred
# - ESP Tracking sync uop was issued
@ - SSE instruction followed an AVX256/AVX512 instruction, dozens of cycles penalty is expected
X - instruction not supported, was not accounted in Analysis

| Num Of   |                    Ports pressure in cycles                         |      |
|  Uops    |  0  - DV    |  1   |  2  -  D    |  3  -  D    |  4   |  5   |  6   |  7   |
-----------------------------------------------------------------------------------------
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm4, xmm3, xmm0
|   1      |             | 1.0  |             |             |      |      |      |      | vpand xmm5, xmm4, xmm1
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm2, xmm1
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm5, xmm5, xmm0
|   1      |             | 1.0  |             |             |      |      |      |      | vpor xmm0, xmm3, xmm0
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm0, xmm0, xmm1
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm1, xmm4, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm3, xmm4, xmm2
|   1      |             |      |             |             |      | 1.0  |      |      | vpor xmm2, xmm5, xmm2
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm2, xmm2, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpcmpeqd xmm4, xmm4, xmm4
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm1, xmm4
|   1      | 1.0         |      |             |             |      |      |      |      | vpor xmm1, xmm5, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm4, xmm5, xmm3
|   1      |             |      |             |             |      | 1.0  |      |      | vpor xmm3, xmm0, xmm3
|   2^     |             |      | 0.3         | 0.4         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rdx], xmm2
|   2^     |             |      | 0.3         | 0.3         | 1.0  |      |      | 0.4  | vmovdqa xmmword ptr [rcx], xmm0
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm0, xmm4, xmm3
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm0, xmm0, xmm1
|   2^     |             |      | 0.4         | 0.3         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rdi], xmm0
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm0, xmm1, xmm3
|   2^     |             |      | 0.3         | 0.4         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rsi], xmm0
Total Num Of Uops: 26
Analysis Notes:
Backend allocation was stalled due to unavailable allocation resources.

如您所见,在第二个版本中,IACA 表示瓶颈是后端,并且“后端分配因不可用的分配资源而停止”。

两种汇编代码都包含相同的指令,唯一的区别是最后 7 条指令的调度以及它们使用的寄存器。

我能想到的唯一可以解释为什么第二个代码更慢的事实是它xmm0在最后 4 条指令中写入了两次,从而引入了依赖关系。但由于这些写入是独立的,我希望 CPU 为它们使用不同的物理寄存器。但是,我无法真正证明这个理论。此外,如果像这样使用两次xmm0是一个问题,我希望 Clang 为其中一条指令使用不同的寄存器(特别是因为这里的寄存器压力很低)。

我的问题:第二个代码应该更慢(基于汇编代码),为什么?

编辑:IACA 跟踪:

第一版:https
://pastebin.com/qGXHVW6a 第二版:https ://pastebin.com/dbBNWsc2


注意:C 代码是 Serpent 密码的第一个 S-box 的实现,由 Osvik在此处计算。

4

2 回答 2

6

弄清楚为什么第二个代码是后端绑定需要一些手动分析,因为 IACA 发出的输出过于原始,尽管信息非常丰富。请注意,IACA 发出的跟踪对于分析循环特别有用。它们对于理解如何执行直线指令序列也很有用(这不是很有用),但是发出的跟踪需要以不同的方式解释。通过这个答案的其余部分,我将展示我对循环场景的分析,这更难做到。

您在没有将代码放入循环的情况下发出跟踪的事实会影响以下内容:

  • 编译器无法内联和优化输出操作数的存储。它们根本不会出现在真正的循环中,或者如果将其链接到不同的 S-box。
  • 从输出到输入的数据依赖是巧合,因为编译器使用 xmm0..3 准备要存储的数据,而不是选择将哪个输出反馈到同一个 S-box 的哪个输入的结果。
  • 内联vpcmpeqd后,创建全为向量(表示 NOT)的将被提升出循环。
  • 会有一个dec/jnz或等效的循环开销(可以宏融合到端口 6 的单个 uop 中)。

但是您已经要求 IACA 分析这个确切的 asm 块,就好像它在循环中运行一样。所以为了解释结果,这就是我们的想法(即使如果你在循环中使用这个函数,这不是你从 C 编译器得到的)。

在这种情况下, Ajmpdec/jnz底部的循环不是问题:它将始终在端口 6 上执行,任何向量指令都不会使用该端口。这意味着跳转指令不会在端口 6 上竞争,也不会消耗调度程序的 uop 带宽,否则这些带宽会被其他指令使用。但是,这可能会影响问题/重命名阶段的资源分配器带宽(每个周期不超过 4 个融合域 uops),但这在我将讨论的这种特殊情况下并不重要。

我们先来看看端口压力ASCII图:

| Num Of   |                    Ports pressure in cycles                         |      |
|  Uops    |  0  - DV    |  1   |  2  -  D    |  3  -  D    |  4   |  5   |  6   |  7   |
-----------------------------------------------------------------------------------------
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm4, xmm3, xmm0
|   1      |             | 1.0  |             |             |      |      |      |      | vpand xmm5, xmm4, xmm1
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm2, xmm1
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm5, xmm5, xmm0
|   1      |             | 1.0  |             |             |      |      |      |      | vpor xmm0, xmm3, xmm0
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm0, xmm0, xmm1
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm1, xmm4, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm3, xmm4, xmm2
|   1      |             |      |             |             |      | 1.0  |      |      | vpor xmm2, xmm5, xmm2
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm2, xmm2, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpcmpeqd xmm4, xmm4, xmm4
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm1, xmm1, xmm4
|   1      | 1.0         |      |             |             |      |      |      |      | vpor xmm1, xmm5, xmm1
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm4, xmm5, xmm3
|   1      |             |      |             |             |      | 1.0  |      |      | vpor xmm3, xmm0, xmm3
|   2^     |             |      | 0.3         | 0.4         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rdx], xmm2
|   2^     |             |      | 0.3         | 0.3         | 1.0  |      |      | 0.4  | vmovdqa xmmword ptr [rcx], xmm0
|   1      | 1.0         |      |             |             |      |      |      |      | vpxor xmm0, xmm4, xmm3
|   1      |             | 1.0  |             |             |      |      |      |      | vpxor xmm0, xmm0, xmm1
|   2^     |             |      | 0.4         | 0.3         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rdi], xmm0
|   1      |             |      |             |             |      | 1.0  |      |      | vpxor xmm0, xmm1, xmm3
|   2^     |             |      | 0.3         | 0.4         | 1.0  |      |      | 0.3  | vmovdqa xmmword ptr [rsi], xmm0

融合域微指令的总数为 22。6 个不同的微指令已分配给端口 0、1 和 5 中的每一个。其他 4 个微指令均由 STD 和 STA 微指令组成。STD 需要端口 4。这个分配是合理的。如果我们忽略所有数据依赖关系,调度程序似乎应该能够在每个周期调度至少 3 个融合域微指令。但是,端口 4 可能存在严重争用,这可能导致预留站被填满。根据 IACA,这不是该代码的瓶颈。请注意,如果调度程序能够以某种方式实现等于分配器的最大吞吐量的吞吐量,那么代码只能是前端绑定的。显然,这里不是这种情况。

下一步是仔细检查 IACA 跟踪。我根据trace做了如下的数据流图,比较容易分析。水平黄线根据在同一周期中分配的微指令划分图表。请注意,IACA 总是假设完美的分支预测。另请注意,此划分的准确率约为 99%,但不是 100%。这并不重要,您可以认为它 100% 准确。节点代表融合的微指令,箭头代表数据依赖性(箭头指向目标微指令)。节点的颜色取决于它们所属的循环迭代。为清楚起见,省略了图表顶部箭头的来源。右侧的绿色框包含为相应的微指令执行分配的周期数。所以前一个周期是X,并且当前周期是 X + 1,无论 X 是什么。停止标志表示关联的 uop 在其中一个端口上发生争用。所有的红色停止标志都表示端口 1 上的争用。只有一个不同颜色的其他停止标志表示端口 5 上的争用。存在争用的情况,但为了清楚起见,我将省略它们。箭头有两种颜色:蓝色和红色。那些是关键的。请注意,分配 2 次迭代的指令需要 11 个周期,然后重复分配模式。请记住,Skylake 有 97 个 RS 整体。存在争用的情况,但为了清楚起见,我将省略它们。箭头有两种颜色:蓝色和红色。那些是关键的。请注意,分配 2 次迭代的指令需要 11 个周期,然后重复分配模式。请记住,Skylake 有 97 个 RS 整体。存在争用的情况,但为了清楚起见,我将省略它们。箭头有两种颜色:蓝色和红色。那些是关键的。请注意,分配 2 次迭代的指令需要 11 个周期,然后重复分配模式。请记住,Skylake 有 97 个 RS 整体。

每个分区内节点的位置(“本地”位置)是有意义的。如果两个节点在同一行,并且它们的所有操作数都可用,则意味着它们可以在同一个周期内调度。否则,如果节点不在同一行,那么它们可能不会在同一个周期中被调度。这仅适用于作为一个组一起分配的动态微指令,而不适用于作为不同组的一部分分配的动态微指令,即使它们恰好位于图中的同一分区中。

在此处输入图像描述

我将使用符号(it, in)来标识特定的融合微指令,其中it是从零开始的循环迭代数,in是从零开始的微指令数。IACA 跟踪中最重要的部分是显示 (11, 5) 的流水线阶段的部分:

11| 5|vpxor xmm0, xmm0, xmm1                            :          |         |         |         |         |         |         |         |         |         |         |         |         |         |         
11| 5|    TYPE_OP (1 uops)                              :          |         |         |         |         |         |_A--------------------dw----R-------p  |         |         |         |         |         

这告诉我们,由于资源不可用(在这种情况下,预留站中的条目),此时分配带宽未被充分利用。这意味着调度程序无法维持足够高的未融合微指令吞吐量来跟上前端每个周期 4 个融合微指令。既然 IACA 已经告诉我们代码是后端绑定的,那么显然这种未充分利用的原因不是因为某些长的依赖链或特定执行单元的争用,而是更复杂的事情。所以我们需要做更多的工作来弄清楚发生了什么。我们必须分析过去 (11, 5)。

每次迭代的微指令 1、4、7、10、13、18 都分配给端口 1。在 11 个周期内会发生什么?总共有 12 条微指令需要端口 1,因此不可能在 11 个周期内调度所有微指令,因为它至少需要 12 个周期。不幸的是,需要相同端口的微控制器内部和需要其他端口的微控制器之间的数据依赖性显着加剧了问题。考虑在 11 个周期期间的以下管道流量:

  • 在周期 0: (0, 0) 和 (0, 1) 被分配(以及我们现在不关心的其他微指令)。(0, 1) 依赖于 (0, 0) 的数据。
  • 1: (0, 4) 和 (0, 7) 被分配。假设没有旧的和就绪的微指令分配给端口 0,并且 (0, 0) 的操作数已准备好,则将 (0, 0) 分派到端口 0。端口 1 可能保持空闲,因为 (0, 1) 尚未准备好.
  • 2: (0, 0) 的结果可通过旁路网络获得。此时,(0, 1) 可以并且将被调度。但是,即使 (0, 4) 或 (0, 7) 已准备好,最旧的 uop 也没有分配给端口 1,因此它都被阻塞了。(0, 10) 被分配。
  • 3:(0, 4) 被分派到端口 1。(0, 7) 和 (0, 10) 都被阻塞,即使它们的操作数已准备好。(0, 13) 被分配。
  • 4:(0, 7) 被分派到端口 1。(0, 10) 被阻塞。(0, 13) 必须等待 (0, 7)。(0, 18) 被分配。
  • 5: (0, 10) 被分派到端口 1。 (0, 13) 被阻塞。(0, 18) 必须等待 (0, 17),这取决于 (0, 13)。(1, 0) 和 (1, 1) 被分配。
  • 6:(0, 13) 被分派到端口 1。(0, 18) 必须等待 (0, 17),这取决于 (0, 13)。(1, 1) 必须等待 (1, 0)。(1, 0) 无法调度,因为 (1, 0) 和 (0, 7) 之间的距离是 3 uop,其中一个可能会发生端口冲突。(1, 4) 被分配。
  • 7:没有任何东西被分派到端口 1,因为 (0, 18)、(1, 1) 和 (1, 4) 还没有准备好。(1, 7) 被分配。
  • 8:没有任何东西被分派到端口 1,因为 (0, 18)、(1, 1)、(1, 4) 和 (1, 7) 还没有准备好。(1, 10) 和 (1, 13) 被分配。
  • 9:(0, 18) 被分派到端口 1。(1, 10) 和 (1, 4) 已准备好,但由于端口争用而被阻塞。(1, 1)、(1, 7) 和 (1, 13) 尚未准备好。
  • 10:(1, 1) 被分派到端口 1。(1, 4)、(1, 7) 和 (1, 10) 已准备好,但由于端口争用而被阻塞。(1, 13) 还没有准备好。(1, 18) 被分配。

好吧,理想情况下,我们希望在 11 个周期内将 12 个微指令中的 11 个分派到端口 1。但这一分析表明,情况远非理想。端口 1 在 11 个周期中有 4 个处于空闲状态!如果我们假设前一次迭代中的一些 (X, 18) 在周期 0 被分派,那么端口 1 将空闲 3 个周期,考虑到我们有 12 个微指令每 11 个周期需要它,这是一个很大的浪费。在 12 个微指令中,最多只有 8 个被派出。情况会变得多糟糕?我们可以继续分析跟踪并记录 p1-bound uop 的数量如何准备好调度但由于冲突而阻塞,或者由于数据错误而未准备好。我能够确定由于端口冲突而停止的 p1-bound uops 的数量永远不会大于 3。但是,由于数据质量问题而停止的 p1-bound uops 的数量随着时间的推移总体上逐渐增加。我没有看到它增加的任何模式,所以我决定对跟踪的前 24 个周期使用线性回归来预测在什么时候会有 97 个这样的微指令。下图说明了这一点。

在此处输入图像描述

x 轴表示从左到右增加的从零开始的循环。请注意,前 4 个周期的微指令数为零。y 轴表示相应循环中此类微指令的数量。线性回归方程为:

y = 0.3624x - 0.6925。

通过将 y 设置为 97,我们得到:

x = (97 + 0.6925) / 0.3624 = 269.57

也就是说,大约在第 269 周期时,我们预计 RS 中有 97 个微指令都是 p1-bound 并等待它们的操作数准备好。正是在这一点上,RS 已满。但是,由于其他原因,可能还有其他微指令在 RS 中等待。因此,我们预计分配器在周期 269 或之前未充分利用其带宽。通过查看指令 (11, 5) 的 IACA 跟踪,我们可以看到这种情况发生在周期 61,比 269 早得多。这意味着我的预测要么非常乐观,要么绑定到其他端口的 uops 数量也表现出类似的行为。我的直觉告诉我是后者。但这足以理解为什么 IACA 说代码是后端绑定的。您可以对第一个代码执行类似的分析,以了解它为何受前端约束。我想我

如果 IACA 不支持特定的代码片段,或者当特定微架构不存在像 IACA 这样的工具时,可以执行此手动分析。线性回归模型能够估计分配器在多少次迭代后未充分利用其带宽。例如在这种情况下,循环 269 对应于迭代 269/11/2 = 269/22 = 12。因此,只要最大迭代次数不大于 12 次,循环的后端性能就会小于问题。

@Bee 有一篇相关的帖子:x86 微指令是如何安排的?.

稍后我可能会发布前 24 个周期中发生的情况的详细信息。

旁注:Wikichip 关于Skylake的文章中有两个错误。首先,Broadwell 的调度器有 60 个整体,而不是 64 个。其次,分配器的吞吐量最多只有 4 个融合微指令。

于 2018-08-09T08:39:15.017 回答
3

我对这两个代码进行了基准测试,并没有发现它们之间的任何性能差异。

我在我的 Skylake i7-6700k 上做了同样的事情,实际上是对你告诉 IACA 分析的内容进行基准测试,方法是采用那个 asm 并dec ebp / jnz .loop在它周围拍打。

我发现sbox_ref每次迭代以 ~7.50 个周期sbox_resched运行,而以 ~8.04 c/iter 运行,在 Linux 上的静态可执行文件中进行了测试,并带有性能计数器。(有关我的测试方法的详细信息, 请参阅x86 的 MOV 真的可以“免费”吗?为什么我根本不能重现它? )。IACA 的数字是错误的,但sbox_resched速度较慢是正确的。

Hadi 的分析似乎是正确的:asm 中的依赖链足够长,以至于 uop 调度中的任何资源冲突都会导致后端失去它永远无法赶上的吞吐量。


大概您通过让 C 编译器将该函数内联到一个循环中来进行基准测试,其中输出操作数的局部变量。这将显着改变 asm(这些是我在编写自己的答案之前编辑成@Hadi 答案的要点的反面):

  • 编译器在函数后期使用 xmm0..3 作为临时寄存器,而不是偶然发生,从输出到输入的数据依赖关系对编译器是可见的,因此它可以适当地调度。您的源代码将选择将哪个输出反馈到同一个 S-box 的哪个输入。

    或者 deps 不存在(如果您使用常量输入并避免使用volatile或空的内联 asm 语句优化循环)。

  • 输出操作数的存储优化了,如果将它链接到不同的 S-box 就会发生。

  • 内联vpcmpeqd后,创建全为向量(表示 NOT)的将被提升出循环。

正如 Hadi 所说,1 uop 宏融合dec/jnz循环开销不会与向量 ALU 竞争,因此它本身并不重要。至关重要的是,围绕编译器没有优化为循环体的东西打一个 asm 循环不出所料会产生愚蠢的结果。

于 2019-01-20T18:54:37.740 回答