内存分配性能分析

所有内存分配的低开销(适用于生产环境)统计,按文件和行号跟踪。

用法:kconfig 选项:- CONFIG_MEM_ALLOC_PROFILING

  • CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT

  • CONFIG_MEM_ALLOC_PROFILING_DEBUG 为由于缺少注释而未被统计的分配添加警告

启动参数

sysctl.vm.mem_profiling={0|1|never}[,compressed]

当设置为“never”时,内存分配性能分析开销将降至最低,并且无法在运行时启用(sysctl 变为只读)。当 CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=y 时,默认值为“1”。当 CONFIG_MEM_ALLOC_PROFILING_ENABLED_BY_DEFAULT=n 时,默认值为“never”。“compressed”可选参数将尝试以紧凑格式存储页面标签引用,避免页面扩展。这可以提高性能和内存消耗,但是可能会因系统配置而失败。如果压缩失败,将发出警告并且内存分配性能分析将被禁用。

sysctl

/proc/sys/vm/mem_profiling

运行时信息

/proc/allocinfo

示例输出

root@moria-kvm:~# sort -g /proc/allocinfo|tail|numfmt --to=iec
      2.8M    22648 fs/kernfs/dir.c:615 func:__kernfs_new_node
      3.8M      953 mm/memory.c:4214 func:alloc_anon_folio
      4.0M     1010 drivers/staging/ctagmod/ctagmod.c:20 [ctagmod] func:ctagmod_start
      4.1M        4 net/netfilter/nf_conntrack_core.c:2567 func:nf_ct_alloc_hashtable
      6.0M     1532 mm/filemap.c:1919 func:__filemap_get_folio
      8.8M     2785 kernel/fork.c:307 func:alloc_thread_stack_node
       13M      234 block/blk-mq.c:3421 func:blk_mq_alloc_rqs
       14M     3520 mm/mm_init.c:2530 func:alloc_large_system_hash
       15M     3656 mm/readahead.c:247 func:page_cache_ra_unbounded
       55M     4887 mm/slub.c:2259 func:alloc_slab_page
      122M    31168 mm/page_ext.c:270 func:alloc_page_ext

操作原理

内存分配性能分析建立在代码标记的基础之上,代码标记是一个用于声明静态结构体(通常以某种方式描述文件和行号,因此称为代码标记),然后在运行时查找和操作它们的库,例如,迭代它们以在 debugfs/procfs 中打印它们。

为了添加分配调用的统计,我们将其替换为宏调用 alloc_hooks(),该宏调用 - 声明一个代码标签 - 在 task_struct 中保存一个指向它的指针 - 调用真正的分配函数 - 最后,将 task_struct alloc 标签指针恢复为其先前的值。

这允许嵌套 alloc_hooks() 调用,最近的一个生效。这对于 mm/ 代码内部的分配非常重要,这些分配不属于外部分配上下文,应该单独计数:例如,slab 对象扩展向量,或者 slab 从页面分配器分配页面时。

因此,正确的用法需要确定应标记分配调用堆栈中的哪个函数。有许多辅助函数本质上包装了例如 kmalloc() 并做更多的工作,然后在多个地方被调用;我们通常希望在这些辅助函数的调用者中进行统计,而不是在辅助函数本身中。

要修复给定的辅助函数,例如 foo(),请执行以下操作: - 将其分配调用切换到 _noprof() 版本,例如 kmalloc_noprof()

  • 将其重命名为 foo_noprof()

  • 像这样定义一个 foo() 的宏版本

    #define foo(...) alloc_hooks(foo_noprof(__VA_ARGS__))

也可以在您自己的数据结构中保存指向 alloc 标签的指针。

当您实现一个“代表”其他代码进行分配的通用数据结构时,请这样做 - 例如,rhashtable 代码。这样,我们就不会在 /proc/allocinfo 中看到 rhashtable.c 的一大行,而是可以按 rhashtable 类型将其分解。

为此: - 像任何其他分配函数一样,挂钩您数据结构的 init 函数。

  • 在您的 init 函数中,使用方便的宏 alloc_tag_record() 在您的数据结构中记录 alloc 标签。

  • 然后,对您的分配使用以下形式:alloc_hooks_tag(ht->your_saved_tag, kmalloc_noprof(...))