Linux环境下程序的多核CPU占用率高的问题分析和解决
1.项目问题
前端PDC双目倾斜相机客流统计项目中排查平台服务程序延时大的问题时,平台demo程序测试发现多核cpu中的某个核的占用率达到100%,导致组件中的目标检测线程和客流统计线程的单帧耗时达不到实时,存在延时和丢帧的问题。通过使用strace、pstack进行程序分析,最终找到导致单核占用率很高的原因和解决方法。本文详细描述了该问题的排查过程,并对排查流程做了相关总结。
2.背景技术及术语解释
在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率,也就是说,任务管理器中显示的是每个刷新周期内CPU占用率的统计平均值。
3.方案详细描述
1)CPU占用率概念,以及top命令和tegrastats命令的使用
在任务管理器的一个刷新周期内,CPU忙(执行应用程序)的时间和刷新周期总时间的比率,就是CPU的占用率,也就是说,任务管理器中显示的是每个刷新周期内CPU占用率的统计平均值。top命令经常用来监控linux的系统状况,比如cpu、内存的使用,显示各进程(任务)的状态监控。
如图1所示。
PID — 进程id
USER — 进程所有者
PR — 进程优先级
NI — nice值。负值表示高优先级,正值表示低优先级
VIRT — 进程使用的虚拟内存总量,单位kb。VIRT=SWAP+RES
RES — 进程使用的、未被换出的物理内存大小,单位kb。RES=CODE+DATA
SHR — 共享内存大小,单位kb
S — 进程状态。
D = 不可中断的睡眠状态
R = 运行
S = 睡眠
T = 跟踪/停止
Z = 僵尸进程
%CPU — 上次更新到现在的CPU时间占用百分比
%MEM — 进程使用的物理内存百分比
TIME+ — 进程使用的CPU时间总计,单位1/100秒
COMMAND — 进程名称(命令名/命令行)示各个进程(任务)
./tegrastats命令经常用来监控linux的系统显存使用情况,也包含cpu、内存的使用状态,对设备的状态进行监控和显示。如图2所示。
RAM — 内存占用
SWAP — 交换区
cache — 缓存
cpu — cpu各个核心的资源占用
EMC — 内存频率
AVP — 音频资源
VDE — 视频资源
GR3D — GPU消耗
2)CPU占用率过高的问题现象和排查过程
前端PDC双目倾斜相机客流统计项目中排查平台服务程序延时大的问题时,平台demo程序测试发现多核cpu中的某个核的占用率达到100%,导致组件中的目标检测线程和客流统计线程的单帧耗时达不到实时,存在延时和丢帧的问题。Top和tegrastats命令执行,得到的状态如下图3所示。
图3 cpu占用率高的top和tegrastats命令的显示状态
因为这是个多线程的进程,实际上占用cpu的最小单位是线程,所以肯定是众线程中的某一个或几个占用CPU过 高 导 致 的 。 用 下 面 的 命 令 将cpu占 用 率 高 的 线 程 找 出 来:ps H -eo user,pid,ppid,tid,time,%cpu,cmd --sort=%cpu
直接使用ps Hh-eo pid,tid,pcpu | sort-nk3 |tail 获取对于的进程号和线程号,如下图4所示。
如上图所示我们可以看出id为17758的线程cpu占用率最高。运行strace命令,可以看到每个线程的
调用堆栈。如下图6所示。
图6strace命令各个线程的堆栈状态
执行pstack脚本,看该线程的调用堆栈,很容易看出在哪一步逻辑上导致了busy loop。如下图7所示。
图7pstack脚本找出导致busyloop的代码行
根据提示,找到对应的代码行,消除busy loop代码,解决cpu占用率高的问题。
3)避免CPU高占用率的方法
通过以上分析,平台demo程序中的main函数中的死循环占用了大量的cpu资源,导致多核cpu中的某个核的占用率达到100%。通过修改demo程序,去掉busy loop,可以避免单核占用率达到100%的问题。同时针对多核cpu中存在0核或者其他核的占用率高的问题,将多个线程处理过程绑定到不同的cpu上,使得多核cpu负载均匀,降低线程处理的耗时。绑核前后的0核cpu占用率见下图8所示。
图8绑核前后的0核cpu占用率
4)正确的线程绑核
Cpu绑核时,应将子线程绑定到不同的cpu核,是cpu的各个核负载均衡。如果将主线程绑核,会导致子线程的绑核不生效。如下图所示。
图9主线程不绑核cpu占用率
图10主线程绑0核cpu占用率
图11主线程绑1核cpu占用率线程的cpu
绑核是通过设置线程的调度亲缘性来实现的。因为Linux操作系统下进程和线程的创建都是通过系统调用clone来实现的,所以实际上调度亲缘性也是被用pthread_create创建的线程所继承的。这意味着,如果主线程在创建其它线程之前设定亲缘性,那么它所设定的亲缘性将被继承,因为这时所有线程的亲缘性相同。
总结:
(1)程序中的高CPU占用率问题,可以通过strace工具和pstack脚本进行定位;
(2)CPU的高占用率可以通过消除代码中的busyloop和线程绑核的方式避免或使多核CPU负载均匀;
(3)多核CPU绑定时,应避免将主线程进行绑核,将处理子线程分别绑定至不同的CPU核,可以实现CPU负载均匀,提高运行效率;