C 内存复制基准测试的吞吐量分析

C 内存复制基准测试的吞吐量分析,c,performance,x86,intel,perf,C,Performance,X86,Intel,Perf,我正在使用一个高大小参数(~1GB)对以下copy函数进行基准测试(不是很花哨!): 现在,由于我的LLC是~25MB,而我正在复制~1GB,所以这段代码是内存受限的性能通过大量暂停的前端循环来确认这一点: 6914888857 cycles # 2,994 GHz 4846745064 stalled-cycles-frontend # 70,09%

我正在使用一个高
大小
参数(~1GB)对以下
copy
函数进行基准测试(不是很花哨!):

现在,由于我的LLC是~25MB,而我正在复制~1GB,所以这段代码是内存受限的<代码>性能
通过大量暂停的前端循环来确认这一点:

        6914888857      cycles                    #    2,994 GHz                    
        4846745064      stalled-cycles-frontend   #   70,09% frontend cycles idle   
   <not supported>      stalled-cycles-backend   
        8025064266      instructions              #    1,16  insns per cycle        
                                                  #    0,60  stalled cycles per insn
最后,但并非最不重要的一点:我知道英特尔可以流水线指令;这种带有内存操作数的
mov
也是这样吗

多谢各位

我的第一个问题是每个指令大约0.60个暂停周期。对于这样的代码来说,这似乎是一个非常低的数字,因为数据没有缓存,所以它们总是访问LLC/DRAM。由于LLC延迟为30个周期,主内存约为100个周期,如何实现这一点

我的第二个问题是相关的;预取器似乎做得比较好(不足为奇,它是一个阵列,但仍然如此):我们60%的时间是通过LLC而不是DRAM实现的。不过,其他时候它失败的原因是什么?解缆的哪个带宽/部分导致此预取器无法完成其任务

使用预取器。具体地说,根据它是哪个CPU,可能有一个“TLB预取器”获取虚拟内存转换,加上一个缓存线预取器从RAM获取数据到L3,再加上一个从L3获取数据的L1或L2预取器

请注意,缓存(如L3)工作于物理地址,其硬件预取器工作于检测和预取对物理地址的顺序访问,并且由于虚拟内存管理/分页,物理访问在页面边界上“几乎从不”连续。由于这个原因,预取器在页面边界停止预取,可能需要三次“非预取”访问才能从下一页开始预取

还要注意的是,如果RAM较慢(或代码较快),预取程序将无法跟上,并且您会暂停更多。对于现代多核机器,RAM的速度通常足以跟上一个CPU的速度,但无法跟上所有CPU的速度。这意味着,在“受控测试条件”之外(例如,当用户同时运行50个进程且所有CPU都在冲击RAM时),您的基准测试将完全错误。还有一些事情,比如IRQ、任务切换和页面错误,它们可能会干扰(特别是当计算机处于负载状态时)

最后,但并非最不重要的一点:我知道英特尔可以流水线指令;这种带有内存操作数的mov也是这样吗

对,;但涉及内存的正常
mov
(例如
mov字节ptr[rdi+rax*1],cl
)也将受到“使用存储转发进行写排序”内存排序规则的限制

请注意,有许多方法可以加快复制速度,包括使用非时态存储(故意破坏/绕过内存排序规则)、使用
rep mov
(在可能的情况下,它经过特别优化,可以在整个缓存线上工作)、使用更大的块(例如,AVX2一次复制32个字节)、自己进行预取(特别是在页面边界),以及进行缓存刷新(以便在复制完成后缓存仍然包含有用的内容)


然而,做相反的事情要好得多——故意让大拷贝速度非常慢,这样程序员会注意到它们很糟糕,并“被迫”试图找到一种避免复制的方法。避免复制20个MiB可能需要0个周期,这比“最差”要快得多备选方案。

TL;DR:在未使用的域中总共有5个UOP(请参见:)。常春藤桥上的环路流检测器无法跨环路体边界分配UOP(请参见:),因此分配一次迭代需要两个周期。在双插座Xeon E5-2680 v2上,环路实际运行速度为2.3c/iter(每个插座10个核,而您的12个),因此,考虑到前端瓶颈,这接近可以做到的最好

预取器的性能非常好,大部分时间循环没有内存限制。每2个周期复制1个字节的速度非常慢。(gcc做得很差,应该给你一个循环,每个时钟可以运行1次迭代。如果没有配置文件引导优化,即使是
-O3
也不能启用
-funroll循环
,但它可以使用一些技巧(如将负索引向上计数到零,或对相对于存储的负载进行索引并增加目标指针),这将使循环下降到4个UOP。)

每次迭代额外的.3个周期平均比前端瓶颈慢,这可能是由于预取失败时的暂停(可能在页面边界),或者可能是由于在
.data
部分的静态初始化内存上运行的页面错误和TLB未命中


循环中有两个数据依赖项。首先,存储指令(特别是STD uop)取决于加载指令的结果。其次,存储和加载指令都取决于
add rax,0x1
。事实上,
add rax,0x1
也取决于自身。由于
add rax,0x1
的延迟是一个周期,因此循环性能的上限是每次迭代一个周期

由于存储(STD)取决于负载,因此在负载完成之前无法从RS调度存储,这至少需要4个周期(L1命中的情况下)。此外,只有一个端口可以接受STD UOP,但在Ivy网桥上每个周期最多可以完成两个负载(尤其是在两个加载都是针对驻留在一级缓存中的行,并且没有发生组冲突的情况下),导致额外的争用。但是,
资源暂停。任何
都表明RS实际从未满。
IDQ\u UOPS\u未交付。CORE
统计未使用的问题插槽数量。这等于所有插槽的36%。活动的
LSD.CYCLES\u
事件表明LSD在大多数情况下用于交付UOPSe时间。但是,

movzx ecx, byte ptr [rsi+rax*1]
mov byte ptr [rdi+rax*1], cl
add rax, 0x1
cmp rdx, rax
jnz 0xffffffffffffffea
        6914888857      cycles                    #    2,994 GHz                    
        4846745064      stalled-cycles-frontend   #   70,09% frontend cycles idle   
   <not supported>      stalled-cycles-backend   
        8025064266      instructions              #    1,16  insns per cycle        
                                                  #    0,60  stalled cycles per insn
          83788617      LLC-loads                                                    [50,03%]
          50635539      LLC-load-misses           #   60,43% of all LL-cache hits    [50,04%]
          27288251      LLC-prefetches                                               [49,99%]
          24735951      LLC-prefetch-misses                                          [49,97%]
BITS 64
DEFAULT REL

section .data
bufdest:    times COUNT db 1 
bufsrc:     times COUNT db 1

section .text
global _start
_start:
    lea rdi, [bufdest]
    lea rsi, [bufsrc]

    mov rdx, COUNT
    mov rax, 0

.loop:
    movzx ecx, byte [rsi+rax*1]
    mov byte [rdi+rax*1], cl
    add rax, 1
    cmp rdx, rax
    jnz .loop

    xor edi,edi
    mov eax,231
    syscall