考虑以下代码:
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
得到以下结果:
# perf stat -B -e l2_request_g1.all_no_prefetch:k,l2_request_g1.l2_hw_pf:k,cache-references:k ./main sample.txt
Performance counter stats for './main sample.txt':
651,263,793 l2_request_g1.all_no_prefetch:k
600,476,712 l2_request_g1.l2_hw_pf:k
1,251,740,542 cache-references:k
当我注释掉
read(fd, buffer, buf_size);
时,我得到以下信息:
36,037,824 l2_request_g1.all_no_prefetch:k
33,416,410 l2_request_g1.l2_hw_pf:k
69,454,244 cache-references:k
查看缓存行大小,我得到以下结果(索引 0-3 相同):
# cat /sys/devices/system/cpu/cpu0/cache/index3/coherency_line_size
64
启用透明大页支持 (THP):
# cat /sys/kernel/mm/transparent_hugepage/enabled
[always] madvise never
我已经检查过大页面是在运行时分配的。将硬件预取访问放在一边,在我看来,
read
负责超过 3 GiB 的缓存引用:
64 x (651,263,793 - 36,037,824) / (1024^3 x 10) = 3.6 GiB
我现在想知道读取 1 GiB 文件如何生成 3.6 GiB 的内存流量。
[更新]有关系统的更多信息:
它在配备 AMD EPYC 7H12 64 核处理器的双路服务器上运行。 Linux内核版本为6.8.0-41,发行版为Ubuntu 24.04.1 LTS。我使用以下命令编译代码:
# gcc -D_GNU_SOURCE main.c -o main
文件系统是 ZFS:
# df -Th
Filesystem Type Size Used Avail Use% Mounted on
...
home zfs x.yT xyzG x.yT xy% /home
当我删除
O_DIRECT
时,我得到以下结果(与包含它时没有显着不同):
650,395,869 l2_request_g1.all_no_prefetch:k
599,548,912 l2_request_g1.l2_hw_pf:k
1,249,944,793 cache-references:k
最后,如果我将
malloc
替换为 valloc
,我会得到以下结果(同样,与原始值没有太大区别):
651,092,248 l2_request_g1.all_no_prefetch:k
558,542,553 l2_request_g1.l2_hw_pf:k
1,209,634,821 cache-references:k
您正在使用 ZFS,但您的 Linux 内核几乎肯定不支持 ZFS 上的
O_DIRECT
。 https://www.phoronix.com/news/OpenZFS-Direct-IO 表示 OpenZFS 仅在 5 天前将对它的支持合并到主线中,因此除非发行版内核较早获得了 2020 年补丁,否则 O_DIRECT
可能只是默默无视。
这可能解释了您的 L2 流量结果大约是您读取大小的 4 倍。 两个副本(ZFS 的 ARC 到页面缓存,页面缓存到用户空间),每次读取+写入整个数据。 或者只有一个
copy_to_user
如果不是 避免 MESI RFO(读取所有权) 则必须在使用新存储的值更新目标之前将目标读入缓存,因此总流量是副本大小的 3 倍。 副本的额外 0.6 可能来自进入页面缓存的初始副本,以及程序运行时发生的其他 L2 流量。
ZFS 还可能进行额外的读取来验证数据的校验和(不仅仅是元数据)。 希望他们能在某种程度上缓存阻止,这样他们就能获得 L1d 或至少 L2 命中,但我不知道。 但该验证只需在从实际磁盘读取数据后进行,并且可能完全忽略 O_DIRECT,数据只会在页面缓存和/或 ARC 中保持热状态。 IDK 如果任何校验和发生在内核线程中而不是在您自己的进程中,
perf stat
(没有-a
)会对其进行计数。
像 XFS 和 EXT4 这样的文件系统绝对支持
O_DIRECT
。 您将需要 valloc
或 aligned_alloc
:用于大分配的 Glibc malloc
使用 mmap
获取新页面,并使用其中的前 16 个字节作为其簿记元数据,因此大分配对于 32 和 32 的每次对齐都是不对齐的更大,包括页面大小。
支持压缩(如 BTRFS)的 FS 也无法对压缩文件以及 ZFS / BTRFS 校验和数据执行 O_DIRECT
操作,它们必须在某些时候进行验证。 XFS 仅校验元数据。
因此,如果
current
工作,您实际上预计不会因为该 I/O 而计数,除非您使用系统范围模式 (O_DIRECT
)。 也许仅当您计算 DRAM 或 L3 的事件时。 或者,对于来自 perf stat -a
的 L2 中的一些热数据,必须在下一次 DMA 之前将其逐出。
x86 DMA 是缓存一致的(因为早期的 CPU 没有缓存,并且要求软件在 DMA 无法向后兼容之前失效)。 英特尔至强甚至可以直接 DMA 进入 L3,而不仅仅是写回并使任何缓存数据无效。 我不知道AMD Zen是否有类似的功能。 由于每个核心集群 (CCX) 都有自己的 L3,因此它必须知道以哪个 L3 为目标最有用。