這個方法也許可以讓你的攝像頭預覽更加流暢

背景

由於在某監控安防大廠工作,前端時間遇到了一個設備性能問題,就是設備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次的數據庫操作。這樣是非常不合理的。後面經過我的修改,變成了以下的邏輯:
在這裏插入圖片描述

1.先獲取數據庫中過期的人數,並將人員用戶名保存在數組中。(用戶名爲唯一標識)
2.根據過期的人數以及用戶名進行刪除操作。
這樣簡單的修改,就有極大的改善,原先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線程:
另一個具有更高優先級的FIFO線程就緒
正在執行的FIFO線程因爲等待一個事件(如I/O)而被阻塞
正在執行的FIFO線程通過調用SCHED_yield原語放棄處理器
當一個FIFO線程被中斷後,它被放置在一個與優先級相關聯的隊列中
當一個FIFO線程就緒,並且如果該線程的優先級比當前正在處理的線程擁有更高的優先級時,當前被執行的線程被搶佔,具有更高優先級且就緒的FIFO線程
開始執行,如果多個線程都具有更高的優先級,則選擇等待時間最長的線程

SCHED_RR

對於RR類,每個線程都有一個時間量與之關聯(就是我們常聽到的時間片),當一個RR線程在它的時間量裏執行結束之後,它被掛起,然後調度器選擇一個具有相同或更高優先級的實時線程運行。
在這裏插入圖片描述

SCHED_FIFO:D->B->C->A

SCHED_RR: D->D->B->C->B->C->A

SCHED_OTHER

對於SCHED_OTHER類,我們需要注意的是,它實際運行起來的優先級和時間片是動態變化的;

  1. 時間片的範圍是10~200ms,一般而言,具有較高優先級的任務分配的時間片也比較大
  2. 動態優先級是靜態優先級和執行行爲的函數計算出來的,一般來說,大部分時間處於睡眠的線程擁有較高優先級

簡單瞭解了各個調度策略之後,我們進一步看一下我們的問題。於是我就通過下面的命令查看了各個線程的調度策略和優先級:

awk '{print $1,$2,$40,$41}' /proc/PID/task/*/stat 

結果發現線程的調度策略大部分都是 0 ,並且優先級都是0。僅有幾個線程的調度策略是實時FIFO調度策略。我們上述說過分時調度算法(SCHED_OTHER)的優先級和時間片是動態變化的。並且還有FIFO線程可能來進行搶佔,這當然會導致我們預覽效果卡頓。於是我就將預覽的線程改爲了實時策略(RR),只需要在線程中添加如下代碼即可:

 struct sched_param sp={0};
 int policy = SCHED_RR;
 sp.sched_priority = 60;
 if (0 == pthread_setschedparam(pthread_self(), policy, &sp)) {
    printf("IO Thread #%u using high-priority scheduler!", pthread_self());
 }
   else{
    printf("pthread_setschedparam fail,pthread_self() = %u,%d\n",pthread_self(),errno);
   }

編譯,燒錄,運行。發現調度策略和優先級的確是修改成功了。預覽效果也有所提高,但是仍然會有卡頓。

思考數據源

到了這裏,我覺得預覽線程應該已經獲取到很高的cpu支配權限了,但是爲什麼仍然會卡呢。我於是想是不是數據源慢了?由於DSP向緩衝區放數據慢了,導致應用取不到數據,造成卡頓。於是和楊工商量了一下,讓他把產生數據的線程設置爲RR優先級爲85,如下圖:
···
352 (Enc_drvStream) 85 2
353 (DemuxStreamThr) 85 2
···
結果得到了很大的改善,進本都是很流暢的,只是偶然會有幾路卡頓。

優化應用代碼

到了這一步,我覺得只有幾路會偶然卡頓,應該就不是數據源的問題了,如果是數據源的問題,一般都會是6路一起卡頓。思考方向就在應用了,通過分析,將一些打印和sleep時間進行修改,基本就不會卡頓了。其中需要注意的是打印的代碼,雖然我們代碼中打印設置了打印等級,但是有些不必要的代碼仍然會被執行,造成浪費;
在這裏插入圖片描述

如圖,紅色方框內就會被執行。

最終在其他方面也進行一些修修改改,最終的效果還是比較滿意的。基本的思路就是這樣的。希望對大家有所幫助

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章