作者在某监控安防大厂工作,前段时间遇到了一个设备性能问题。就是设备6路预览时,画面卡单不连贯,体验极差。领导让我解决这个问题。经过几天的学习和探讨,问题是解决了。虽然其中涉及到的知识点并不多,但整个过程并不容易,故在此做出总结进行分享,希望能对遇到类似问题的朋友有所帮助。
分析原因
首先,给我们直观的感受是画面卡段,但是一路预览并不卡顿,随着预览路数增加,才会造成越来越卡顿的现象。通过分析,随着预览路数的增加,设备的CPU使用率也会上升,并且6路时,CPU达到90%以上。因此我的解决方向,放在了降低设备CPU使用率上。
分析资源消耗
一个设备中可能有许多进程,一个进程中可以有许多的线程,我们如何进行选择呢?这大概是许多人刚开始较为头疼的问题。我们可以使用下列命令查看设备各个线程的使用率。(一般的ps命令是经过裁减的,这个是通过源码进行编译的,需要的朋友可以在资源里下载)
./ps -L c -e -o pid,tid,pcpu,cmd
值得注意的是,该命令得到的CPU使用率是设备从启动到当前的平均值,所以当时间变长时,该值会趋向于平缓。建议取10分钟的数据。通过这样的方式,我们可以直观的看到哪些线程消耗的资源较多,并且不合理。我们就可以去修改那些线程了。
从何入手?
当确认了需要修改的线程,很多人就会立刻去相应的线程代码中去看代码,希望从一行一行的代码中找到优化的点。可以是一种方式,但是我并不建议。因为这样没有侧重点,当然效率也就不会高。我建议先找到资源消耗的地方,再去重点走读代码,找到优化的地方。我们知道程序运行的过程中,存在两种状态,一个是应用态,一个是内核态。如果我们知道两种状态消耗的资源份额,就会知道重点去从哪个方向走读代码。
linux 下一切皆文件
秉承着该核心思想,通过在网上查询,发现proc文件系统的确给我们提供了这样的查看方式:
awk '{print $1,$2,$14,$15}' /proc/PID/task/PPID/stat
通过上述的命令,我们可以分贝看到该线程的ID,线程名,用户态占用的时间,内核态占用的时间(强烈建议大家了解一下stat文件中各字段的意义,以后肯定会有用)。
如果是用户态占用时间较多,说明该线程的时间消耗在算数运算或者是一些内存操作,数据库操作中;如果是内核态占用时间较多,说明是IO操作较多,比如文件操作,read,write等。
举例:应用态较多
如图,我们得知时间主要消耗在应用态并且10分钟的CPU使用率大致在7%左右。而该线程的主要功能是定时查看数据库中注册用户是否过期,代码的整体框架大致如图:
其中MAX_REGIST_USER的值为1000,其实该代码的逻辑就存在很大的问题,因为无论什么情况下,该循环至少会执行3000次的数据库操作。这样是非常不合理的。后面经过我的修改,变成了以下的逻辑:
- 先获取数据库中过期的人数,并将人员用户名保存在数组中。(用户名为唯一标识);
- 根据过期的人数以及用户名进行删除操作。
这样简单的修改,就有极大的改善,原先7%的cpu使用率,现在只有0.1%左右。这其实就是内存换时间的思想。当然我这个肯定不是最优的方案。
举例:内核态较多
该线程是我们预览的线程,可知cpu的消耗主要是集中在内核态。应该就是IO操作比较多了。由于代码逻辑较为复杂,篇幅较多等原因,我这里就不贴图了。
分析代码逻辑,发现整个线程中只有一个writev接口,也就是说这有这一个IO操作。并且线程是将每一个rtp包weite一次,于是我就尝试将6个包发送一次,从而降低write的频率。果不其然,这样CPU就降下来了,从原先的7%降到了4.5%。
以上就是分别从内核态和应用态分析的思路,当然用户态和内核态如果都进行优化当然是最好的了。
CPU降下来了,预览效果不达标
通过上述的修改,设备在6路预览时,CPU使用率大致在80%左右。但是预览时,依旧会明显卡顿,难道是CPU高的原因?我心中怀疑了一下(当然,CPU继续往下降,预览是不会卡的了)。于是我就将前端设备的CPU使用率搞到了90%进行6路预览,惊奇的发现,前端设备6路预览并不卡,非常流畅。于是我的思考方向不在是降CPU使用率。
线程间的调度策略和优先级
在进行下一步的讲解前,我先简单介绍一下调度策略的相关内容。linux下一共有三种调度策略。
- SCHED_FIFO 先进先出实时调度策略;
- SCHED_RR 轮转实时调度策略;
- SCHED_OTHER 其他非实时调度策略。
每个调度策略都是有优先级的,实时类的大于分时类的。默认的配置是,实时优先级099,分时的优先级在100139之间。
SHCED_FIFO
对于FIFO类,有以下规则:
除非在以下情况下,系统不会中断一个正在执行的FIFO线程:
(1) 另一个具有更高优先级的FIFO线程就绪;
(2) 正在执行的FIFO线程因为等待一个事件(如I/O)而被阻塞;
(3) 正在执行的FIFO线程通过调用SCHED_yield原语放弃处理器;
(4) 当一个FIFO线程被中断后,它被放置在一个与优先级相关联的队列中;
(5) 当一个FIFO线程就绪,并且如果该线程的优先级比当前正在处理的线程拥有更高的优先级时,当前被执行的线程被抢占,具有更高优先级且就绪的FIFO线程;
(6) 开始执行,如果多个线程都具有更高的优先级,则选择等待时间最长的线程。
SCHED_RR
对于RR类,每个线程都有一个时间量与之关联(就是我们常听到的时间片),当一个RR线程在它的时间量里执行结束之后,它被挂起,然后调度器选择一个具有相同或更高优先级的实时线程运行。
a. SCHED_FIFO:D->B->C->A
b. SCHED_RR: D->D->B->C->B->C->A
SCHED_OTHER
对于SCHED_OTHER类,我们需要注意的是,它实际运行起来的优先级和时间片是动态变化的。
- 时间片的范围是10~200ms,一般而言,具有较高优先级的任务分配的时间片也比较大;
- 动态优先级是静态优先级和执行行为的函数计算出来的,一般来说,大部分时间处于睡眠的线程拥有较高优先级。
简单了解了各个调度策略之后,我们进一步看一下我们的问题。于是我就通过下面的命令查看了各个线程的调度策略和优先级:
awk '{print $1,$2,$40,$41}' /proc/PID/task/*/stat
结果发现线程的调度策略大部分都是 0 ,并且优先级都是0。仅有几个线程的调度策略是实时FIFO调度策略。我们上述说过分时调度算法(SCHED_OTHER)的优先级和时间片是动态变化的。并且还有FIFO线程可能来进行抢占,这当然会导致我们预览效果卡顿。于是我就将预览的线程改为了实时策略(RR),只需要在线程中添加如下代码即可:
编译,烧录,运行。发现调度策略和优先级的确是修改成功了。预览效果也有所提高,但是仍然会有卡顿。
思考数据源
到了这里,我觉得预览线程应该已经获取到很高的cpu支配权限了,但是为什么仍然会卡呢。我于是想是不是数据源慢了?由于DSP向缓冲区放数据慢了,导致应用取不到数据,造成卡顿。于是和杨工商量了一下,让他把产生数据的线程设置为RR优先级为85,如下:
···
352 (Enc_drvStream) 85 2
353 (DemuxStreamThr) 85 2
···
结果得到了很大的改善,进本都是很流畅的,只是偶然会有几路卡顿。
优化应用代码
到了这一步,我觉得只有几路会偶然卡顿,应该就不是数据源的问题了,如果是数据源的问题,一般都会是6路一起卡顿。思考方向就在应用了,通过分析,将一些打印和sleep时间进行修改,基本就不会卡顿了。其中需要注意的是打印的代码,虽然我们代码中打印设置了打印等级,但是有些不必要的代码仍然会被执行,造成浪费;
如图,红色方框内就会被执行。
最终在其他方面也进行一些修修改改,最终的效果还是比较满意的。基本的思路就是这样的。希望对大家有所帮助。
本文作者:谢艺华
授权出处:跟老林学智嵌