追踪PR_SET_THP_DISABLE

Posted by Fei Wu on January 17, 2024

背景

在sophgo 2042上测试lkp vm-scalability里面的anon-wx-rand-mt用例时出现regression,主要是调用一个usemem的程序

  • case 1 - 通过lkp跑能重现性能差的问题,lkp run anon-wx-rand-mt.yaml
  • case 2 - 直接跑usemem性能很好,usemem –runtime 90 -t 64 –prealloc -random 1055820736

我本来希望能用最简单的usemem复现问题,这样问题域较小,然而事与愿违。从lkp在调用到usemem之前还比较复杂,我们直接调试来看看什么情况。

这个文档不涉及该regression本身的定位,而是看看上面2个操作的不同。

简单分析

首先可以看到

  • case 1 所有cpu到在%sys,性能差的原因
  • case 2 绝大部份cpu在%usr

对case 1 做个简单的profiling,可以看到95%的时间都是在pagefault。

分别查看2个case的pagefaults的情况,对于case 1 使用命令

1
2
3
4
5
$ perf stat -efaults lkp run anon-wx-rand-mt.yaml

 Performance counter stats for 'lkp run anon-wx-rand-mt.yaml':

    1677146     faults

对于case 2 使用同样方法

1
2
 Performance counter stats for '/lkp/benchmarks/vm-scalability/usemem --runtime 90 -t 64 --prealloc --random 1055820736':
       1179     faults

很容易看到pagefault number的巨大差异,来源很可能是hugepage

  • 在运行过程中通过/proc/pid/smaps可以确认这一点
  • 系统级别的/sys/kernel/mm/transparent_hugepage/enabled 即使设置成always对case 1 也不起作用

追踪

稍微看下内核代码,可以找到hugepage_vma_check是分配thp的必经之处,并且可以进行kprobe

1
# grep hugepage_vma_check /sys/kernel/debug/tracing/available_filter_functions

我们主要来观察case 1 为什么不能申请到thp,使用bpftrace trace一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env bpftrace

kprobe:hugepage_vma_check
/comm == "usemem"/
{
    @vm_flags[arg1] = count();
    @vm_mm_flags[comm, ((struct vm_area_struct *)arg0)->vm_mm->flags] = count();
}

kretprobe:hugepage_vma_check
/comm == "usemem"/
{
    @ret[retval] = count();
}

结果如下:

1
2
3
4
5
6
7
@ret[0]: 651
@vm_flags[1048947]: 1
@vm_flags[113]: 1
@vm_flags[117]: 5
@vm_flags[112]: 64
@vm_flags[1048691]: 580
@vm_mm_flags[usemem, 16777421]: 651

可以看出check失败的原因是test_bit(MMF_DISABLE_THP, &vma->vm_mm->flags),并且只有prctl()系统调用才会设置这个flag

我们继续通过strace来trace系统调用,可以看到lkp会调用

1
2
3
4440 execve("/usr/local/bin/lkp", ["lkp", "run", "anon-wx-rand-mt.yaml"], 0x3ff01e5178 /* 45 vars */) = 0
...
4440 prctl(PR_SET_THP_DISABLE, 1, 0, 0, 0) = 0

关于PR_SET_THP_DISABLE的解释参考手册,这个方法应该用的不是很广泛,通过搜索引擎或者chatgpt查找disable thp,都没有PR_SET_THP_DISABLE,即使加了process级别。如果代码里面需要控制thp,除了下面说的情况,madvise可能用得更多。

1
2
3
4
5
6
7
8
PR_SET_THP_DISABLE (since Linux 3.15)
       Set  the  state  of  the  "THP  disable" flag for the calling thread.
       If arg2 has a nonzero value, the flag is set, otherwise it is cleared.
       Setting this flag provides a method for disabling transparent huge pages
       for jobs where the code cannot be modified, and using a malloc hook with
       madvise(2) is not an option (i.e., statically allocated data).  The
       setting of the "THP disable" flag is inherited by a child created via
       fork(2) and is preserved across execve(2).

总结

  • 通过trace工具我们可以在对代码不算熟悉的时候就能找到问题所在
  • lkp测试用例的一些设置需要额外关注,使用lkp本身命令来复现问题可靠性更高,对于不熟悉的人同时也引入了一些复杂度