我在IvyBridge上,想测试L1d缓存组织。我的理解如下:
在IvyBridge上,L1d缓存具有32K容量、64B缓存行、8路集合关联。因此,它有32K/(64*8)=64个集合,给定主存储器addr
,集合索引可以由(addr/64) % 64
计算。
因此,如果我将主内存增加64*64(4K),我将始终触摸相同的L1d集。一个集合只有8个缓存行,因此,如果我用16个步骤循环它,我将获得几乎100%的L1d缓存未命中。
我写了以下程序来验证:
section .bss
align 4096
buf: resb 1<<26
%define gap 64 * 64 ; no L1 cache miss
; %define gap 64 * 64 * 256 ; 41% L1 cache miss
; %define gap 64 * 64 * 512 ; 95% L1 cache miss
; however, total cycle suggests this gap is already at L3 latency level with complete L2 cache miss.
section .text
global _start
_start:
mov rcx, 10000000
xor rax, rax
loop:
mov rax, [buf+rax]
mov rax, [buf+rax+gap*1]
mov rax, [buf+rax+gap*2]
mov rax, [buf+rax+gap*3]
mov rax, [buf+rax+gap*4]
mov rax, [buf+rax+gap*5]
mov rax, [buf+rax+gap*6]
mov rax, [buf+rax+gap*7]
mov rax, [buf+rax+gap*8]
mov rax, [buf+rax+gap*9]
mov rax, [buf+rax+gap*10]
mov rax, [buf+rax+gap*11]
mov rax, [buf+rax+gap*12]
mov rax, [buf+rax+gap*13]
mov rax, [buf+rax+gap*14]
mov rax, [buf+rax+gap*15]
dec rcx,
jne loop
xor rdi, rdi
mov rax, 60
syscall
令我惊讶的是,perf
显示根本没有丢失L1缓存:
160,494,057 L1-dcache-loads
4,290 L1-dcache-load-misses # 0.00% of all L1-dcache hits
我的理解有什么错?
所有BSS页面最初都会在写入时拷贝映射到同一个物理零页面。您将得到TLB未命中(可能还有软页面错误),但没有L1d未命中。
为了避免这种情况,并将它们映射到不同的物理页面:
- 首先通过向每页写入一个字节来清除它们
- 可以使用CCD_ 4而不是使用BSS进行分配。这至少可以预先设置故障,避免软页面故障,但可能仍然是同一个物理零页面
- 将
buf
放在.data
或.rodata
部分中,在那里它实际上将使用文件备份进行映射。(你必须把它做得更小,因为零实际上会在可执行文件中)
(对我来说)更有趣的结果是,确实开始以更大的步幅获得缓存未命中。那么,你访问的4k页面总数会更多,这可能会导致你的内核开始为你的BSS使用2M的hugepage,具有讽刺意味的是,让它们不再是同一个4k物理页面的别名,从而伤害了它。您可以检查/proc/PID/smaps
以查看是否存在非零AnonHuge用于该映射。
L2失误是意料之中的事,因为它也只有8路关联,但L3更具关联性,并且使用了一个非简单的索引函数,该函数将2步的任何简单幂分布在多个集合上。(英特尔酷睿i7处理器使用了哪种缓存映射技术?)
顺便说一句,你可能想要一个不是2次方的差距。只是L1混叠步长的倍数,而不是L2混叠步长,因此数据可以分布在L2中的多个集合中。
我一直在寻找重复的,但没有找到确切的,尽管我很确定我以前在SO>上解释过这一点<。也许我在想如何在这个循环中获得持续的高吞吐量?与malloc完全相同的问题,而不是BSS。
相关:
- 当调用realloc时,现代操作系统可能会跳过复制,这是真的吗?这对
mremap
的零页虚拟内存/COW有一些好处 - malloc是否为Linux(和其他平台)上的分配惰性地创建备份页?(延迟分配/最初甚至不设置页面表与COW映射到零页面是分开的。即使在第一次触摸页面时进行只读访问,它也可以始终分配真实页面。)
- 书面复制究竟是如何工作的