为什么我看到使用 REP MOVSB 的 RFO(读取所有权)请求比使用 vmovdqa 的请求多

Noa*_*oah 5 x86-64 intel cpu-architecture memcpy micro-optimization

结帐 Edit3

我得到了错误的结果,因为我在测量时没有包括这里讨论的预取触发事件。话虽如此,AFAIKrep movsb与临时存储相比,我只看到 RFO 请求减少,memcpy因为在加载时预取更好,而没有对存储进行预取。不是因为 RFO 请求针对完整缓存行存储进行了优化。这种有意义的,因为我们没有看到RFO请求优化掉了vmovdqa一个zmm寄存器,我们预计如果真的在那里为整个缓存线存储情况。话虽如此,存储上缺乏预取和非临时写入的缺乏使得很难看出如何rep movsb具有合理的性能。

编辑:RFO 可能来自rep movsb不同的请求vmovdqa,因为rep movsb它可能不请求数据,只需在独占状态下取行即可。对于有收银机的商店,情况也可能如此zmm。但是,我没有看到任何性能指标来测试这一点。有谁知道吗?

问题

  1. 为什么我没有看到RFO请求减少时,我使用rep movsbmemcpy作为相比,memcpy与实现的vmovdqa
  2. 为什么我看到越来越多的RFO请求时,我用rep movsbmemcpy作为相比,memcpy与实现vmovdqa

两个单独的问题,因为我相信我应该看到 RFO 请求减少了rep movsb,但如果不是这种情况,我是否也应该看到增加?

背景

CPU - Icelake: Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz

我试图在使用不同的方法时测试 RFO 请求的数量,memcpy包括:

并且无法看到使用rep movsb. 事实上,我看到的 RFO 请求rep movsb比 Temporal Stores 多。鉴于共识理解似乎是 ivybridge 和 newrep movsb能够避免 RFO 请求,从而节省内存带宽,这是违反直觉的:

当发出 rep movs 指令时,CPU 知道要传输已知大小的整个块。这可以帮助它以离散指令无法实现的方式优化操作,例如:

  • 当知道整个缓存行将被覆盖时避免 RFO 请求。

请注意,在 Ivybridge 和 Haswell 上,如果缓冲区足够大以适合 MLC,您可以使用 rep movsb 击败 movntdqa;movntdqa 导致对 LLC 的 RFO,rep movsb 没有

我编写了一个简单的测试程序来验证这一点,但无法这样做。

测试程序

#include <assert.h>
#include <errno.h>
#include <immintrin.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>

#define BENCH_ATTR __attribute__((noinline, noclone, aligned(4096)))


#define TEMPORAL          0
#define NON_TEMPORAL      1
#define REP_MOVSB         2
#define NONE_OF_THE_ABOVE 3

#define TODO 1

#if TODO == NON_TEMPORAL
#define store(x, y) _mm256_stream_si256((__m256i *)(x), y)
#else
#define store(x, y) _mm256_store_si256((__m256i *)(x), y)
#endif

#define load(x)     _mm256_load_si256((__m256i *)(x))

void *
mmapw(uint64_t sz) {
    void * p = mmap(NULL, sz, PROT_READ | PROT_WRITE,
                    MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    assert(p != NULL);
    return p;
}
void BENCH_ATTR
bench() {
    uint64_t len = 64UL * (1UL << 22);

    uint64_t len_alloc = len;
    char *   dst_alloc = (char *)mmapw(len);
    char *   src_alloc = (char *)mmapw(len);

    for (uint64_t i = 0; i < len; i += 4096) {
        // page in before testing. perf metrics appear to still come through
        dst_alloc[i] = 0;
        src_alloc[i] = 0;
    }

    uint64_t dst     = (uint64_t)dst_alloc;
    uint64_t src     = (uint64_t)src_alloc;
    uint64_t dst_end = dst + len;



    asm volatile("lfence" : : : "memory");
#if TODO == REP_MOVSB
    // test rep movsb
    asm volatile("rep movsb" : "+D"(dst), "+S"(src), "+c"(len) : : "memory");
#elif TODO == TEMPORAL || TODO == NON_TEMPORAL
    // test vmovtndq or vmovdqa
    for (; dst < dst_end;) {
        __m256i lo = load(src);
        __m256i hi = load(src + 32);
        store(dst, lo);
        store(dst + 32, hi);
        dst += 64;
        src += 64;
    }
#endif

    asm volatile("lfence\n\tmfence" : : : "memory");

    assert(!munmap(dst_alloc, len_alloc));
    assert(!munmap(src_alloc, len_alloc));
}

int
main(int argc, char ** argv) {
    bench();
}

Run Code Online (Sandbox Code Playgroud)
  • 构建(假设文件名为rfo_test.c):
gcc -O3 -march=native -mtune=native rfo_test.c -o rfo_test
Run Code Online (Sandbox Code Playgroud)
  • 运行(假设可执行文件是rfo_test):
perf stat -e cpu-cycles -e l2_rqsts.all_rfo -e offcore_requests_outstanding.cycles_with_demand_rfo -e offcore_requests.demand_rfo ./rfo_test
Run Code Online (Sandbox Code Playgroud)

测试数据

注意:edit2中噪声较小的数据

  • TODO = 时间
       583,912,867      cpu-cycles
         9,352,817      l2_rqsts.all_rfo
       188,343,479      offcore_requests_outstanding.cycles_with_demand_rfo
        11,560,370      offcore_requests.demand_rfo

       0.166557783 seconds time elapsed

       0.044670000 seconds user
       0.121828000 seconds sys
Run Code Online (Sandbox Code Playgroud)
  • 待办事项 = NON_TEMPORAL
       560,933,296      cpu-cycles
         7,428,210      l2_rqsts.all_rfo
       123,174,665      offcore_requests_outstanding.cycles_with_demand_rfo
         8,402,627      offcore_requests.demand_rfo

       0.156790873 seconds time elapsed

       0.032157000 seconds user
       0.124608000 seconds sys
Run Code Online (Sandbox Code Playgroud)
  • TODO = REP_MOVSB
       566,898,220      cpu-cycles
        11,626,162      l2_rqsts.all_rfo
       178,043,659      offcore_requests_outstanding.cycles_with_demand_rfo
        12,611,324      offcore_requests.demand_rfo

       0.163038739 seconds time elapsed

       0.040749000 seconds user
       0.122248000 seconds sys
Run Code Online (Sandbox Code Playgroud)
  • 待办事项 = NONE_OF_THE_ABOVE
       521,061,304      cpu-cycles
         7,527,122      l2_rqsts.all_rfo
       123,132,321      offcore_requests_outstanding.cycles_with_demand_rfo
         8,426,613      offcore_requests.demand_rfo

       0.139873929 seconds time elapsed

       0.007991000 seconds user
       0.131854000 seconds sys

Run Code Online (Sandbox Code Playgroud)

检测结果

只有安装但没有基线RFO请求memcpy是在TODO = NONE_OF_THE_ABOVE7527122个RFO请求。

通过TODO = TEMPORAL(使用vmovdqa)我们可以看到9,352,817 个RFO 请求。这比TODO = REP_MOVSB(using rep movsb) 有11,626,162 个RFO 请求要低。使用rep movsb比使用临时存储多约 200 万个 RFO 请求。我能够看到避免 RFO 请求的唯一情况是TODO = NON_TEMPORAL(using vmovntdq),它有7,428,210 个RFO 请求,与表明没有来自memcpy自身的基线大致相同。

我为 memcpy 尝试了不同的大小,认为我可能需要减少/增加大小rep movsb以进行优化,但我一直看到相同的一般结果。对于我测试的所有尺寸,我看到 RFO 请求的数量按以下顺序NON_TEMPORAL< TEMPORAL< REP_MOVSB

理论

  • [不太可能] Icelake 有什么新东西吗?

编辑:@PeterCordes 能够在 Skylake 上重现结果

我不认为这是一个Icelake具体的东西作为唯一的变化我可以在中找到英特尔手册rep movsb的Icelake是:

从基于 Ice Lake Client 微架构的处理器开始,REP MOVSB 短操作的性能得到增强。增强适用于 1 到 128 个字节之间的字符串长度。CPUID 特性标志列举了对 fast-short REP MOVSB 的支持:CPUID [EAX=7H, ECX=0H).EDX.FAST_SHORT_REP_MOVSB[bit 4] = 1。REP STOS 性能没有变化。

鉴于len远高于 128 ,这不应该在我使用的测试程序中发挥作用。

  • [Likelier] 我的测试程序坏了

我没有看到任何问题,但这是一个非常令人惊讶的结果。至少验证了编译器没有优化这里的测试

编辑:修复了使用的构建指令G++而不是GCC文件后缀从.c.cc

编辑2:

回到 C 和 GCC。

  • 更好的偏好食谱
perf stat --all-user -e cpu-cycles -e l2_rqsts.all_rfo -e offcore_requests_outstanding.cycles_with_demand_rfo -e offcore_requests.demand_rfo ./rfo_test
Run Code Online (Sandbox Code Playgroud)

具有更好性能配方的数字(趋势相同但噪音更小):

  • TODO = 时间
       161,214,341      cpu-cycles                                                  
         1,984,998      l2_rqsts.all_rfo                                            
        61,238,129      offcore_requests_outstanding.cycles_with_demand_rfo                                   
         3,161,504      offcore_requests.demand_rfo                                   

       0.169413413 seconds time elapsed

       0.044371000 seconds user
       0.125045000 seconds sys
Run Code Online (Sandbox Code Playgroud)
  • 待办事项 = NON_TEMPORAL
       142,689,742      cpu-cycles                                                  
             3,106      l2_rqsts.all_rfo                                            
             4,581      offcore_requests_outstanding.cycles_with_demand_rfo                                   
                30      offcore_requests.demand_rfo                                   

       0.166300952 seconds time elapsed

       0.032462000 seconds user
       0.133907000 seconds sys
Run Code Online (Sandbox Code Playgroud)
  • TODO = REP_MOVSB
       150,630,752      cpu-cycles                                                  
         4,194,202      l2_rqsts.all_rfo                                            
        54,764,929      offcore_requests_outstanding.cycles_with_demand_rfo                                   
         4,194,016      offcore_requests.demand_rfo                                   

       0.166844489 seconds time elapsed

       0.036620000 seconds user
       0.130205000 seconds sys
Run Code Online (Sandbox Code Playgroud)
  • 待办事项 = NONE_OF_THE_ABOVE
        89,611,571      cpu-cycles                                                  
               321      l2_rqsts.all_rfo                                            
             3,936      offcore_requests_outstanding.cycles_with_demand_rfo                                   
                19      offcore_requests.demand_rfo                                   

       0.142347046 seconds time elapsed

       0.016264000 seconds user
       0.126046000 seconds sys
Run Code Online (Sandbox Code Playgroud)

Edit3:这可能与隐藏 L2 Prefetcher 触发的 RFO 事件有关

我使用了 @BeeOnRope 制作的 pref 配方,其中包括由 L2 Prefetcher 启动的 RFO 事件:

perf stat --all-user -e cpu/event=0x24,umask=0xff,name=l2_rqsts_references/,cpu/event=0x24,umask=0xf2,name=l2_rqsts_all_rfo/,cpu/event=0x24,umask=0xd2,name=l2_rqsts_rfo_hit/,cpu/event=0x24,umask=0x32,name=l2_rqsts_rfo_miss/ ./rfo_test
Run Code Online (Sandbox Code Playgroud)

没有 L2 预取事件的等效性能配方:

perf stat --all-user -e cpu/event=0x24,umask=0xef,name=l2_rqsts_references/,cpu/event=0x24,umask=0xe2,name=l2_rqsts_all_rfo/,cpu/event=0x24,umask=0xc2,name=l2_rqsts_rfo_hit/,cpu/event=0x24,umask=0x22,name=l2_rqsts_rfo_miss/ ./rfo_test
Run Code Online (Sandbox Code Playgroud)

并得到了更合理的结果:

Tl;博士; 使用预取数字,我们看到更少的 RFO 请求rep movsb。但它似乎并没有rep movsb真正避免 RFO 请求,而只是接触较少的缓存行

包含和不包含预取触发事件的数据

待办事项 = 性能事件 带预取 无预取 区别
--------------- --------------- --------------- --------------- ---------------
l2_rqsts_references 16812993 4358692 12454301
l2_rqsts_all_rfo 14443392 1981560 12461832
l2_rqsts_rfo_hit 1297932 1038243 259689
l2_rqsts_rfo_miss 13145460 943317 12202143
--------------- --------------- --------------- --------------- ---------------
非临时 l2_rqsts_references 8820287 1946591 6873696
非临时 l2_rqsts_all_rfo 6852605 346 6852259
非临时 l2_rqsts_rfo_hit 66845 317 66528
非临时 l2_rqsts_rfo_miss 6785760 29 6785731
--------------- --------------- --------------- --------------- ---------------
REP_MOVSB l2_rqsts_references 11856549 7400277 4456272
REP_MOVSB l2_rqsts_all_rfo 8633330 4194510 4438820
REP_MOVSB l2_rqsts_rfo_hit 1394372 546 1393826
REP_MOVSB l2_rqsts_rfo_miss 7238958 4193964 3044994
--------------- --------------- --------------- --------------- ---------------
LOAD_ONLY_TEMPORAL l2_rqsts_references 6058269 619924 5438345
LOAD_ONLY_TEMPORAL l2_rqsts_all_rfo 5103905 337 5103568
LOAD_ONLY_TEMPORAL l2_rqsts_rfo_hit 438518 311 438207
LOAD_ONLY_TEMPORAL l2_rqsts_rfo_miss 4665387 26 4665361
--------------- --------------- --------------- --------------- ---------------
STORE_ONLY_TEMPORAL l2_rqsts_references 8069068 837616 7231452
STORE_ONLY_TEMPORAL l2_rqsts_all_rfo 8033854 802969 7230885
STORE_ONLY_TEMPORAL l2_rqsts_rfo_hit 585938 576955 8983
STORE_ONLY_TEMPORAL l2_rqsts_rfo_miss 7447916 226014 7221902
--------------- --------------- --------------- --------------- ---------------
STORE_ONLY_REP_STOSB l2_rqsts_references 4296169 4228643 67526
STORE_ONLY_REP_STOSB l2_rqsts_all_rfo 4261756 4194548 67208
STORE_ONLY_REP_STOSB l2_rqsts_rfo_hit 17337 309 17028
STORE_ONLY_REP_STOSB l2_rqsts_rfo_miss 4244419 4194239 50180
--------------- --------------- --------------- --------------- ---------------
STORE_ONLY_NON_TEMPORAL l2_rqsts_references 99713 36112 63601
STORE_ONLY_NON_TEMPORAL l2_rqsts_all_rfo 64148 427 63721
STORE_ONLY_NON_TEMPORAL l2_rqsts_rfo_hit 17091 398 16693
STORE_ONLY_NON_TEMPORAL l2_rqsts_rfo_miss 47057 29 47028
--------------- --------------- --------------- --------------- ---------------
以上都不是 l2_rqsts_references 74074 27656 46418
以上都不是 l2_rqsts_all_rfo 46833 375 46458
以上都不是 l2_rqsts_rfo_hit 16366 344 16022
以上都不是 l2_rqsts_rfo_miss 30467 31 30436

似乎大多数 RFO 差异归结为为 memcpy预取增强型 REP MOVSB

立即准确地发出预取​​请求。硬件预取在检测类似 memcpy 的模式方面做得很好,但它仍然需要几次读取才能启动,并且会“过度预取”超出复制区域末尾的许多缓存行。rep movsb 确切地知道区域大小并且可以准确地预取。

商店

这一切似乎都归结为rep movsb不预取存储地址导致需要 RFO 请求的行减少。通过STORE_ONLY_REP_STOSB 我们可以更好地了解 RFO 请求的保存位置rep movsb(假设这两个实现方式相似)。在不计算预取事件的情况下,我们看到rep movsbRFO 请求的数量与rep stosb(以及 HITS / MISSES 的细分相同)大致相同。它有大约 250 万个额外的 L2 引用,可以公平地归因于负载。

这些STORE_ONLY_REP_STOSB数字特别有趣的是,它们几乎不会随着预取数据与非预取数据而变化。这让我觉得rep stosb至少不是预取商店地址。这也对应一个事实,即我们看到的几乎没有RFO_HITS,几乎完全RFO_MISSES。另一方面,临时存储 memcpy 是预取存储地址,因此原始数字出现偏差,因为它们没有vmovdqa计算来自rep movsb.

另一个有趣的指针是STORE_ONLY_REP_STOSBSTORE_ONLY_NON_TEMORAL. 这让我认为rep movsb/rep stosb只是在存储上保存 RFO 请求,因为它没有进行额外的预取,而是使用通过缓存的临时存储。我很难调和的一件事是,似乎来自rep movsb/rep stosb两者都没有预取的存储不使用包含 RFO 的非临时存储,因此我不确定它的性能如何。

负载

我认为rep movsb是预取负载,它在标准vmovdqa循环中做得更好。如果您查看rep movsbw/ 和 w/o 预取和差异之间的差异,LOAD_ONLY_TEMPORAL您会看到大致相同的模式,参考数字LOAD_ONLY_TEMPORAL高出约 20%,但点击数低。这将表明vmovdqa循环正在执行超过尾部的额外预取并且预取效率较低。因此rep movsb,预取加载地址的工作做得更好(因此总引用更少,命中率更高)。

结果

以下是我从数据中想到的:

  • rep movsb 不会优化给定加载/存储的 RFO 请求
    • 也许它是一种不同类型的 RFO 请求,不需要发送数据,但无法找到计数器来对此进行测试。
  • rep movsb不预取存储并且不使用非临时存储。因此,它对存储使用较少的 RFO 请求,因为它不会通过预取引入不必要的行。
    • 可能是期望存储缓冲区隐藏将行放入缓存的延迟,因为它知道从不依赖于存储的值。
    • 启发式可能是另一个核心数据的错误失效太昂贵了,所以它不想为 E/M 状态预取行。
    • 我很难将这与“良好的表现”相协调
  • rep movsb 预取加载并且比正常的时间加载循环做得更好。

编辑4:

使用新的性能配方来衡量非核心读/写:

perf stat -a -e "uncore_imc/event=0x01,name=data_reads/" -e "uncore_imc/event=0x02,name=data_writes/" ./rfo_test
Run Code Online (Sandbox Code Playgroud)

这个想法是如果rep stosb发送 RFO-ND 那么它应该具有与movntdq. 情况似乎如此。

  • TODO = STORE_ONLY_REP_STOSB
        24,251,861      data_reads                                                  
        52,130,870      data_writes                                                 
Run Code Online (Sandbox Code Playgroud)
  • TODO = STORE_ONLY_TEMPORAL
    • 注意:这是通过vmovdqa ymm, (%reg). 这不是 64 字节的存储,因此应该需要带有数据的 RFO。我确实对此进行了测试,vmodqa32 zmm, (%reg)并看到了大致相同的数字。这意味着 1)zmm商店没有经过优化以跳过 RFO 以支持 ItoM,或者 2) 这些事件并不表明我认为它们是什么小心
        39,785,140      data_reads                                                  
        35,225,418      data_writes                                                 
Run Code Online (Sandbox Code Playgroud)
  • TODO = STORE_ONLY_NON_TEMPORAL
        22,680,373      data_reads                                                  
        51,057,807      data_writes                                                 
Run Code Online (Sandbox Code Playgroud)

奇怪的一件事是,虽然两者的读取量都较低,STORE_ONLY_NON_TEMPORALSTORE_ONLY_REP_STOSB写入量却较高。

有真名RFO-ND;ItoM

  • RFO:用于写入缓存行的一部分。如果在“我”需要将数据转发给它。
  • ItoM:用于写入完整缓存行。如果在“我”不需要转发给它的数据。

它与 RFO 聚合在一起OFFCORE_REQUESTS.DEMAND_RFO英特尔有一个性能工具,它似乎从 MSR 中汲取了它的价值,但他们不支持 ICL,到目前为止,找不到 ICL 的文档。需要更多地研究如何隔离它。

Edit5:STORE_ONLY_TEMPORAL较早写入较少的原因是零存储消除

我的测量方法的这个问题之一是该选项不uncore_imc支持事件all-user。我稍微改变了 perf 配方以尝试缓解这种情况:

perf stat -D 1000 -C 0 -e "uncore_imc/event=0x01,name=data_reads/" -e "uncore_imc/event=0x02,name=data_wri

归档时间:

查看次数:

225 次

最近记录:

4 年,10 月 前