uop-cache 缺失粒度

St.*_*rio 2 x86 assembly x86-64 cpu-architecture

英特尔优化手册/B.5.7.3

解码的 ICache 中没有部分命中。如果在 32 字节块上查找的任何微操作丢失,则该事务的所有微操作上都会发生解码的 ICache 未命中

uop-cache 未命中真的发生在 32 字节的粒度上吗?

St.*_*rio 5

KbL i7-8550U它的行为就像有每高速缓存行没有部分匹配,而不是一个32字节区域

我实际上运行了更多不同的实验,然后在下面描述,但不可能把它们都放在这里。


英特尔优化手册记录了 uop 缓存包含 L1i:

解码的 ICache 实际上包含在指令缓存和 ITLB 中。

考虑以下

示例 1。

;edi = 1 << 31
align 32
test_uop_cache_hit:
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 

    ;More 8 * nop ax blocks

    dec edi
    jnz test_uop_cache_hit
    ret
Run Code Online (Sandbox Code Playgroud)

收集计数器icache_64b.iftag_hit, idq.dsb_uops,idq.mite_uops我们有以下情节

在此处输入图片说明

uops 情节是合理的。所有的 uops 都是从 dsb 发送的。

第一个图显示每个 L1i 缓存行只有一个标签查找,大小为 64 字节。为了找到 uop 缓存条目,标签查找是必要的。

示例 2。

nop ax在同一缓存行的 8 *个块的中间添加 jmp 。

;edi = 1 << 31
align 64
test_uop_cache_hit:
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    jmp test_uop_cache_hit_1

align 32
test_uop_cache_hit_1:
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 
    nop ax 

    dec edi
    jnz test_uop_cache_hit
    ret
Run Code Online (Sandbox Code Playgroud)

我们有以下情节:

在此处输入图片说明

uop 情节再次合理。从icache_64b.iftag_hit我得出的结论是,预测被采用的分支会导致 li1 标记查找,以便在 uop 缓存中找到相应的条目(即使分支源和目标属于同一行)。有了这个观察Intel Optimization Manual/2.5.5.2

一旦从遗留管道中传送微操作,从解码的 ICache 中获取微操作只能在下一个分支微操作之后恢复。

对我来说看起来很合理。

现在考虑更有趣一点

例 3。

我将使用汇编伪代码来节省空间

align 64
test_uop_cache_hit:
     8 * nop ax

    19 * nop
    jmp test_uop_cache_hit_1  
align 32:
test_uop_cache_hit_1: ;new line starts here
;more 8 * nop ax 19 * nop jmp blocks
    dec edi
    jnz test_uop_cache_hit
    ret 
Run Code Online (Sandbox Code Playgroud)

我们有以下结果

在此处输入图片说明 在此处输入图片说明

这里有趣的是,即使已插入分支微操作并8 * nop ax完美地适合 uop 缓存,它们也不会从 uop 缓存中传送。从图中可以看出,从 uop 缓存传递的唯一微操作是 macro fuseddec-jnz

结果让我觉得如果某个 32 字节区域不适合 uop 缓存,整个缓存行被标记为不包含在 uop 缓存中,下次询问它的任何 32 字节部分时,它将从传统解码管道。

从传统解码管道切换是否需要分支微操作?检查它考虑

例 4。

align 32
test_uop_cache_hit:
    32 * nop
test_uop_cache_hit_0: ;new line start here
    16 * nop ax
    ;more 16 * nop ax
    dec edi          ;new line start here
    jnz test_uop_cache_hit
    ret
Run Code Online (Sandbox Code Playgroud)

这是 dsb 的结果

在此处输入图片说明

很明显,所有的 uops 都是从传统的解码管道传送的。


考虑一些更复杂的例子来检查Example 3.那里的假设是否有效:

一世。

align 32
test_uop_cache_hit:
    6 * nop ax
    test edi, 0x1
    ;ends 64 byte region, misses due to erratum
    ;does not matter for the example
    jnz test_uop_cache_hit_1

    32 * nop
test_uop_cache_hit_1:
    dec edi
    jnz test_uop_cache_hit
    ret
Run Code Online (Sandbox Code Playgroud)

结果是

 1?075?981?881       idq.dsb_uops
50?341?922?587       idq.mite_uops
Run Code Online (Sandbox Code Playgroud)

结果是完全合理的。当分支没有被采用并且32 * nops 被传递时,很明显它们不能适合 uop 缓存。在32 * nop融合的宏dec-jnz从 Legacy Decode Pipeline 交付之后。它适合 uop 缓存,因此下次执行分支时,它将从 dsb 传送。

结果非常接近预期: (1 << 31)/2 = 1073741824

二、

比之前更复杂的例子

align 32
test_uop_cache_hit:
    test edi, 0x1
    jnz test_uop_cache_hit_2
    jmp test_uop_cache_hit_1

;starts new cache line
align 32
test_uop_cache_hit_1:
    8 * nop ax
; 32 byte aligned
test_uop_cache_hit_2:
    6 * nop ax
    nop dword [eax + 1 * eax + 0x1]
    ;End of 32 bytes region
    ;misses due to erratum
    ;Important here
    jmp test_uop_cache_hit_3
test_uop_cache_hit_3:
    dec edi
    jnz test_uop_cache_hit
    ret
Run Code Online (Sandbox Code Playgroud)

结果如下:

 5?385?033?285      idq.dsb_uops
25?815?684?426      idq.mite_uops
Run Code Online (Sandbox Code Playgroud)

结果在意料之中。每次dec edi - jnz test_uop_cache_hit_2获取它时, 它都会跳转到包含jmp它末尾的 32 字节区域。所以它会错过dsb。下次dec edi - jnz test_uop_cache_hit_2没有采取的jmp test_uop_cache_hit_1采取。通常它会击中 dsb,因为8 * nop ax它非常适合它,但请记住,在之前的循环迭代中jmp,32 字节区域的末尾导致未命中。它们都属于同一个缓存行,因此每次迭代都会发生 dsb 未命中。

结果非常接近预期:(1 << 31) + (1 << 31)/2 + (1 << 31) = 5368709120

在保留32 字节对齐的同时nop ax从 32 字节区域中仅删除一个,会导致所有 uops 从 dsb 传送:jmptest_uop_cache_hit_3

29?081?868?658       idq.dsb_uops
     8?887?726      idq.mite_uops
Run Code Online (Sandbox Code Playgroud)

注意:如果预测每个缓存行有 2 个分支,结果将变得非常不可预测,因此很难给出合理的估计。我不清楚为什么。