使用perf stat分析大文件读取的缓存行为与内存流量异常
问题描述
我用以下C语言代码做大文件读取实验:
int main(int argc, char** argv) { int buf_size = 1024*1024*1024; char* buffer = malloc(buf_size); char* buffer2 = malloc(buf_size); for (int i = 0; i < 10; i++){ int fd = open(argv[1], O_DIRECT | O_RDONLY); read(fd, buffer, buf_size); memcpy(buffer2, buffer, buf_size); } free(buffer); free(buffer2); return 0; }
在1 GiB的输入文件上运行程序,用perf stat统计性能计数器,发现read操作带来了超过3.6 GiB的缓存引用,但实际只读取了1 GiB的文件。注释掉read(fd, buffer, buf_size);后,性能计数器结果大幅降低。
补充系统信息:
- 服务器:双路AMD EPYC 7H12
- 系统:Ubuntu 24.04.1 LTS(内核6.8.0-41)
- 编译命令:
gcc -D_GNU_SOURCE main.c -o main - 文件系统:ZFS
- 其他:缓存行大小64字节,Transparent HugePage (THP)已启用且运行时分配了大页;移除
O_DIRECT或替换malloc为valloc后结果无显著变化。
原因分析
- ZFS的特性带来额外内存访问:ZFS为保证数据完整性,读取数据时会同步计算校验和,这个过程会额外访问存储校验和的内存区域。另外ZFS的ARC缓存不会被
O_DIRECT完全绕过——一次read操作会把数据同时写入用户态buffer和ARC缓存,相当于1GiB数据被加载两次,直接拉高缓存引用量。 - NUMA架构的缓存一致性开销:双路AMD EPYC是NUMA架构,
malloc分配的两个1GiB缓冲区很大概率落在不同NUMA节点上。跨节点执行memcpy时,CPU缓存一致性协议会触发缓存行共享、失效等操作,这些操作都会被perf统计为缓存引用,进一步增加总流量。 - 数据复制的二次缓存访问:
memcpy(buffer2, buffer, buf_size)会把buffer里的1GiB数据复制到buffer2,这个过程需要先从缓存读取buffer内容,再写入buffer2对应的缓存行,又是一次完整的1GiB量级缓存操作。 - 巨页与块大小不匹配的额外开销:虽然启用了THP,但ZFS默认块大小(比如128KiB)和巨页粒度(2MiB/1GiB)不匹配,加载数据时可能触发额外的缓存行加载,带来少量但累积的缓存引用增量。
内容的提问来源于stack exchange,提问作者smz
相关产品推荐
相关产品推荐

