使用 perf stat 分析大文件读取中的缓存行为和内存流量

问题描述 投票:0回答:1

考虑以下代码:

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
c performance file memory perf
1个回答
0
投票

您正在使用 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 仅校验元数据。


DMA 不应该接触 L2,除非可能是为了驱逐其覆盖的缓存线,并且当您的进程不在 CPU 核心上时可能会发生,因为它在 I/O 上被阻塞,因此它处于睡眠状态。

因此,如果

current
工作,您实际上预计不会因为该 I/O 而计数,除非您使用系统范围模式 (
O_DIRECT
)。 也许仅当您计算 DRAM 或 L3 的事件时。 或者,对于来自
perf stat -a
 的 L2 中的一些热数据,必须在下一次 DMA 之前将其逐出。
x86 DMA 是缓存一致的(因为早期的 CPU 没有缓存,并且要求软件在 DMA 无法向后兼容之前失效)。  英特尔至强甚至可以直接 DMA 进入 L3,而不仅仅是写回并使任何缓存数据无效。  我不知道AMD Zen是否有类似的功能。  由于每个核心集群 (CCX) 都有自己的 L3,因此它必须知道以哪个 L3 为目标最有用。

© www.soinside.com 2019 - 2024. All rights reserved.