访问1-3个缓存行时PMU无L1缓存负载统计,CPU从何处取数?
L1缓存访问测试中负载计数异常的原因解析
现象回顾
测试访问不同数量的L1缓存行时,发现:
- 访问1-3个缓存行:
L1-dcache-loads事件计数极低,远低于理论循环次数 - 访问4个及以上缓存行:
L1-dcache-loads骤增,且随缓存行数量线性增长 - 缓存行数量未超过L1容量时,miss率均<1%
该现象在AMD Zen2(Ryzen 5 PRO 4650G)和Intel Skylake-SP(Xeon Silver 4116)上均复现,两者L1缓存均为32KB、64字节缓存行。
核心原因:写回缓冲区的容量限制与负载计数规则
这个现象和CPU的**写回缓冲区(Write Buffer)**直接相关,而非AGU单元数量:
读-修改-写操作的计数逻辑
测试代码中的buffer[i_line][0] +=1属于读-修改-写操作:先从内存/缓存读取数据到寄存器,修改后写回。L1-dcache-loads统计的是从L1缓存加载数据到寄存器的事件,但如果数据能在写回缓冲区(或存储队列)中命中,读操作会直接从这些内部缓冲区获取,不会触发L1加载计数。写回缓冲区的容量阈值
x86架构的CPU写回缓冲区通常包含3-4个条目(Zen2和Skylake-SP的写回缓冲区正好是3-4个条目):- 当访问1-3个缓存行时,每个缓存行的写操作对应的条目可以长期驻留在写回缓冲区中,后续的读-修改-写操作直接命中缓冲区,无需从L1缓存加载,因此
L1-dcache-loads计数极低。 - 当访问4个缓存行时,写回缓冲区被占满,旧的条目会被刷入L1缓存,后续的读操作必须从L1缓存加载数据,因此
L1-dcache-loads计数骤增,且与循环次数(repeat * N_LINES)基本匹配。
- 当访问1-3个缓存行时,每个缓存行的写操作对应的条目可以长期驻留在写回缓冲区中,后续的读-修改-写操作直接命中缓冲区,无需从L1缓存加载,因此
与L1组相联无关的原因
你原本预期8个缓存行(对应AMD L1的8路组相联)会有性能变化,但组相联影响的是缓存冲突miss,而当前现象是写操作的内部缓冲区旁路机制导致的,和缓存映射规则无关,因此8个行时不会出现特殊变化。
验证数据匹配
从你提供的统计数据可以直接验证:
- 3个缓存行时:
L1-dcache-loads仅34,374,远低于理论值(1000*128*2*16*16 *3 = 196,608,000),说明绝大多数读操作命中了写回缓冲区。 - 4个缓存行时:
L1-dcache-loads为260,407,342,接近理论值(1000*128*2*16*16 *4 = 262,144,000),说明每个读操作都触发了L1缓存加载。
额外验证方法
如果想进一步确认结论,可以修改测试代码为单纯的读操作:
uint64_t tmp = buffer[i_line][0];
此时不管访问1-3还是4个缓存行,L1-dcache-loads都会接近理论循环次数——因为读操作直接从L1缓存加载(预热后),不会经过写回缓冲区的旁路。
测试相关代码与命令
测试程序
#define N_LINES 1 #define CACHE_LINE_SIZE 64 alignas(CACHE_LINE_SIZE) uint64_t buffer [N_LINES][CACHE_LINE_SIZE / sizeof(uint64_t)]; static long long unsigned repeat = 1000* 128 * 2 * 16 * 16 ; int main(int argc, char* argv[]) { // pin CPU // warmup data, i.e. loop & write in buffer[i][0] = i for (unsigned rep=0; rep<repeat; rep++) { for (unsigned i_line=0; i_line<N_LINES; i_line++) { // touch the first uint64 in the line buffer[i_line][0] += 1; } } }
(注:原代码中i_block为笔误,修正为i_line)
编译与perf命令
g++ -g -O1 ./test_cache.cpp sudo perf stat \ -e task-clock,instructions,cycles,stalled-cycles-frontend,stalled-cycles-backend \ -e L1-dcache-loads,L1-dcache-load-misses,L1-dcache-prefetches \ -- ./a.out
统计数据示例
# 3个缓存行时 34,374 L1-dcache-loads # 1.006 M/sec (35.11%) 2,460 L1-dcache-load-misses # 7.16% of all L1-dcache accesses (35.11%) # 4个缓存行时 260,407,342 L1-dcache-loads # 1.938 G/sec (17.86%) 5,037 L1-dcache-load-misses # 0.00% of all L1-dcache accesses (17.86%)
内容的提问来源于stack exchange,提问作者xealits
相关产品推荐
相关产品推荐

