背景
在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本身命令来复现问题可靠性更高,对于不熟悉的人同时也引入了一些复杂度