:用gst-shark定位插件瓶頸與幀率優(yōu)化)
之前在某邊緣視頻分析設(shè)備上調(diào)DeepStream多路視頻Demo幀率始終上不去。進(jìn)程的CPU、內(nèi)存用top看都很正常單路gst-launch也能跑到滿幀但一旦組成完整業(yè)務(wù)管道就會出現(xiàn)掉幀、延遲抖動、偶發(fā)花屏。后來我把gst-shark接入到DeepStream的管道里才真正看清問題發(fā)生在哪個插件上。這篇就記錄我用gst-shark做DeepStream插件級性能分析的完整方法以及那些指標(biāo)到底怎么讀、怎么用來指導(dǎo)配置優(yōu)化。如果你是剛接觸DeepStream或者正在調(diào)多路視頻分析系統(tǒng)的幀率、延遲這篇文章應(yīng)該能幫你省下不少彎路。我會從安裝工具開始一直講到實測數(shù)據(jù)整理、瓶頸定位、配置調(diào)整前后的對比所有內(nèi)容都來自實際調(diào)優(yōu)過程中的場景你完全可以直接照著做。1. 先想清楚DeepStream里為什么需要插件級性能分析1.1 進(jìn)程級監(jiān)控看不到的細(xì)節(jié)剛?cè)肟覦eepStream的時候我習(xí)慣用top、htop這些工具看系統(tǒng)負(fù)載。這類工具能告訴你整個app進(jìn)程占了多少CPU、多少內(nèi)存但對“哪里慢”幾乎沒有指導(dǎo)意義。原因在于DeepStream是基于GStreamer框架的一條管道里通常有解碼、流合并、推理、追蹤、OSD、渲染等多個插件它們在同一個進(jìn)程的多個線程里運行。top只能看到進(jìn)程總體CPU占用看不到nvinfer是不是占滿了GPU也看不到nvdsosd是不是在CPU上偷偷做了大量工作更看不到每個插件處理一幀到底花了多少毫秒。遇到幀率不達(dá)標(biāo)如果只靠“猜”效率會非常低。因為你不知道瓶頸是在解碼端、推理端、顯示端還是出在某個queue的排隊策略上。DeepStream有幾個內(nèi)置的調(diào)試手段比如env GST_DEBUGdeepstream但輸出的日志信息太原始只能看到時間戳和buffer流動很難量化每個插件的真實消耗。這時候就需要專門的性能分析工具。對GStreamer體系來說gst-shark就是這套工具里比較成熟的一個。1.2 常見瓶頸到底藏在哪里在一條典型的DeepStream管道里瓶頸的位置通常不是固定的。我經(jīng)手過的幾個項目里瓶頸經(jīng)常出現(xiàn)在這幾個地方解碼環(huán)節(jié)。如果輸入源是H.264/H.265視頻流DeepStream會調(diào)用硬解碼器。但有些場景下比如輸入是MJPG或者高分辨率RTSP流解碼器沒有正確配置就會退化成CPU軟解CPU直接被打滿整條管道都跟著變慢。推理環(huán)節(jié)。nvinfer插件負(fù)責(zé)調(diào)用模型做推理它通常是GPU上的大戶。影響它效率的因素很多比如batch大小是否匹配、模型精度是FP32還是FP16/INT8、輸入分辨率是否和模型要求一致、是否頻繁重建engine等。如果nvinfer的輸入分辨率比源流分辨率低很多那在送進(jìn)推理插件之前就會有一段隱性的縮放開銷這段開銷如果放到了某個convert插件里問題就更不容易察覺。多路合并環(huán)節(jié)。nvstreammux把多路視頻合并成batch送給推理插件這個插件的batch-size設(shè)置和實際到達(dá)幀率是否匹配直接決定GPU推理的利用率。如果設(shè)置不當(dāng)可能一路一路地推理完全沒有并聯(lián)加速的效果。OSD和render環(huán)節(jié)。很多人忽略nvdsosd和sink的性能消耗。nvdsosd在CPU模式下做框疊加和字體渲染多路高分辨率下CPU占用不低。如果還疊加了復(fù)雜時間戳、多級追蹤結(jié)果消耗會進(jìn)一步擴(kuò)大。1.3 gst-shark為什么適合DeepStreamgst-shark本身是為GStreamer設(shè)計的性能測量工具包含一組trace插件和一個命令行工具。DeepStream內(nèi)部就是GStreamer管道所以天然兼容。關(guān)鍵點在于它不需要修改你的pipeline代碼而是通過GStreamer的tracer機制注入到運行過程中去收集數(shù)據(jù)。它能同時拿到多維度數(shù)據(jù)CPU占用、GPU占用、處理時間、buffer大小、隊列深度、幀率、延遲等。相比單純看top你能直接看到每個element插件處理一遍數(shù)據(jù)花了多少毫秒每個element的source pad上buffer有多大、流動速率是多少哪些線程占用CPU特別高隊列是不是在堵端到端延遲分布如何有了這些數(shù)據(jù)分析插件效率就是一件很直觀的事。按照我的經(jīng)驗只要不是硬件本身的計算力不夠絕大多數(shù)幀率問題都能靠gst-shark的數(shù)據(jù)反推出具體位置。2. gst-shark部署從源碼裝到跑通的完整準(zhǔn)備2.1 編譯安裝的依賴與配置gst-shark不是DeepStream自帶的組件需要單獨獲取和編譯。編譯本身不復(fù)雜但有一個點值得先提醒DeepStream內(nèi)置的GStreamer版本可能和你系統(tǒng)默認(rèn)安裝的不一致編譯gst-shark前最好確認(rèn)一下頭文件版本避免裝好后插件加載不了。編譯前需要準(zhǔn)備這些依賴meson和ninja構(gòu)建工具glib-2.0開發(fā)包GStreamer 1.0開發(fā)包gstreamer-plugins-base開發(fā)包如果要用GUI可視化界面還需要GTK3開發(fā)包在嵌入式設(shè)備上如果空間緊張我建議編譯時把GUI關(guān)掉只保留命令行trace功能meson build -Dguidisabled ninja -C build ninja -C build install安裝完成后gst-shark的trace插件會放到系統(tǒng)的GStreamer插件目錄下。但如果DeepStream自帶了一套GStreamer環(huán)境你需要把插件路徑指到它對應(yīng)的目錄或者通過環(huán)境變量補充export GST_PLUGIN_PATH/usr/local/lib/gstreamer-1.0:$GST_PLUGIN_PATH這一步很容易踩坑。我遇到過的情況是編譯安裝看起來成功了但DeepStream的app啟動后提示找不到gst-shark插件。原因就是系統(tǒng)的GStreamer和DeepStream內(nèi)置的GStreamer不是同一套搜索路徑。提示不確定插件是否加載成功時可以用gst-inspect-1.0查看插件列表確認(rèn)里面有g(shù)stshark相關(guān)的tracer元件。加載正常再繼續(xù)下面的流程。2.2 兩種啟動注入方式gst-shark提供了兩種使用方式我平時主要用第一種。第一種是環(huán)境變量注入。設(shè)置GST_TRACERS環(huán)境變量然后正常啟動DeepStream應(yīng)用export GST_TRACERScpu-usage;proctime;buffer;fps deepstream-app -c config.txtGStreamer在初始化tracer機制時會自動加載這些trace插件。這種方式的好處是簡單、直觀不需要修改任何代碼。第二種是用gst-shark命令包裝你的應(yīng)用gst-shark deepstream-app -c config.txtgst-shark會以子進(jìn)程方式啟動目標(biāo)命令同時自動設(shè)置trace。這種方式適合一條命令臨時加分析不需要單獨維護(hù)環(huán)境變量。但注意在部分DeepStream版本里應(yīng)用內(nèi)部自己重置了GST_TRACERS包裝方式可能失效所以我自己更傾向于用環(huán)境變量。2.3 輸出配置先用文本模式壓開銷gst-shark除了文本終端輸出還支持CTF格式的trace文件輸出用來配合Eclipse Trace Viewer等工具做更細(xì)致的離線分析。但老實說在邊緣設(shè)備上我不推薦一開始就開CTF。寫trace文件本身有開銷嵌入式設(shè)備的存儲和IO帶寬都有限開著trace文件跑可能你還沒抓到瓶頸先把性能拉低了一截。生產(chǎn)環(huán)境或現(xiàn)場排查時我建議先用文本模式export GST_SHARK_CTF_DISABLE1 export GST_TRACERSproctime;fps只開少量必要的tracer先看整體幀率和各插件處理時間快速判斷方向。確認(rèn)方向和瓶頸后再按需增加buffer、queue-level等tracer縮小范圍。這樣既控制了工具本身的開銷又能分層定位。3. 讀懂gst-shark指標(biāo)插件效率的讀數(shù)邏輯3.1 proctime每個插件的真實處理時間和方差gst-shark里最常用的指標(biāo)是proctime。它給出每個element處理一個buffer的平均耗時、標(biāo)準(zhǔn)差和最大值??磒roctime時要注意一個原則不要只看平均值一定要配合標(biāo)準(zhǔn)差和最大值看。平均值代表穩(wěn)態(tài)性能最大值代表極端情況。如果平均只有5ms但最大30ms說明插件時有尖峰這種抖動在視頻分析場景里往往比平均耗時更影響體驗因為它會造成掉幀。舉個例子。某次調(diào)試中nvinfer的平均proctime只有6ms看起來完全沒問題但stddev高達(dá)4ms遠(yuǎn)高于其他插件。后面定位才發(fā)現(xiàn)推理輸入尺寸和模型的動態(tài)batch設(shè)置不匹配導(dǎo)致部分幀觸發(fā)了engine內(nèi)部的重配置產(chǎn)生了尖峰。光看平均值根本發(fā)現(xiàn)不了這個隱患。proctime指標(biāo)通常會在終端里周期性刷新每次刷新列出各插件名稱、平均時間、標(biāo)準(zhǔn)差、最大時間。看到某個插件明顯偏高就需要進(jìn)一步跟蹤它的輸入輸出判斷是插件本身低效還是上游送數(shù)不均勻。3.2 CPU/GPU占用誰在消耗計算資源cpu-usage tracer按線程顯示CPU占用能看出每個GStreamer線程的負(fù)載情況。在DeepStream的上下文里這個指標(biāo)能幫助判斷一個插件是在GPU上工作還是意外跑到了CPU上。正常來說涉及圖像處理的插件比如nvvideoconvert、nvdsosd如果配置正確GPU負(fù)載應(yīng)該由GPU承擔(dān)CPU占用不會很高。如果你看到某個插件的線程CPU占用始終很高而且proctime也不低那大概率是走了CPU路徑。比如nvvideoconvert輸入了系統(tǒng)內(nèi)存sysmembuffer無法走GPU加速就會退化成CPU做顏色空間轉(zhuǎn)換這個開銷在多路高分辨率下非常驚人。gst-shark還提供gpu-usage tracer能顯示GPU整體占用率。但在部分版本和平臺上這個tracer讀不到數(shù)據(jù)或者默認(rèn)為0。我后面會專門講這個問題。這里想強調(diào)的是CPU和GPU的讀數(shù)要結(jié)合起來看不要單獨看某一個。3.3 buffer與queue-level觀測數(shù)據(jù)流動和阻塞Buffer tracer的輸出看起來像是“哪條pad上有多少數(shù)據(jù)在流動”它能反映上下游插件之間的吞吐量。讀取buffer tracer時我關(guān)注兩個東西buffer大小和buffer速率。舉個例子nvstreammux把4路1080p合成一個batch buffer后輸出buffer的大小應(yīng)該接近4路原始圖像數(shù)據(jù)的總和。如果你發(fā)現(xiàn)muxer輸出的buffer異常小說明某個輸入流可能沒有真正進(jìn)入batch合并邏輯這是排查多路流丟流時很好用的判斷依據(jù)。Queue-level tracer則直接反映隊列深度。GStreamer里的queue插件本來就是為了解耦上下游但如果某個下游處理速度不夠queue的深度會持續(xù)增長。正常情況下queue在使用率和深度上應(yīng)該穩(wěn)定在一個區(qū)間。如果你看到某個queue長期處于接近滿的狀態(tài)說明上游太快或下游太慢瓶頸在下游如果queue一直空著說明上游供給不足問題可能在上游或源頭采集。需要注意的是DeepStream內(nèi)部很多插件之間不是通過顯式queue連接的而是通過GstPad直接連接。這種情況下queue-level trace可能不會覆蓋所有連接點。但只要你理解了這個思路把它和buffer tracer結(jié)合起來一樣能判斷數(shù)據(jù)流動是否順暢。3.4 fps與latency用最終指標(biāo)驗證優(yōu)化fps tracer統(tǒng)計每個src pad上的幀率這是最直觀的結(jié)果指標(biāo)。排查的時候我通常先看整體fps有沒有達(dá)到預(yù)期再結(jié)合proctime追蹤是哪一級拖了后腿。latency tracer給出端到端延遲的估計。方法是通過GStreamer的延遲查詢機制計算從數(shù)據(jù)進(jìn)入管道到離開sink的總時間。DeepStream的sink和部分插件實現(xiàn)了延遲查詢因此這個指標(biāo)通常能拿到。幀率穩(wěn)定但延遲高和幀率波動但延遲低是兩類完全不同的優(yōu)化方向。前者可能是緩沖隊列太深、accumulation時間太長后者可能是某個插件偶爾產(chǎn)生長尾耗時。這兩類問題都能通過fps和latency的組合判斷出來。4. 實操案例多路視頻掉幀后如何通過gst-shark一步步定位并修復(fù)4.1 搭一個可復(fù)現(xiàn)的基線場景為了說清楚整個排查過程我用一個虛構(gòu)但非常典型的場景來演示。假設(shè)我在一臺邊緣設(shè)備上跑DeepStream多路視頻分析輸入是8路1080p H.264視頻每路預(yù)期實時處理目標(biāo)總幀率大于等于25fps。但實際跑起來總幀率只有18fps左右而且偶發(fā)掉幀。第一步我不急著加tracer而是先固定一個可復(fù)現(xiàn)的配置8路視頻源、batch-size設(shè)置為4、模型使用FP16精度、OSD開啟。固定配置的目的是讓后續(xù)所有對比數(shù)據(jù)都有同一個基準(zhǔn)否則你改一個參數(shù)的同時又改了另一個參數(shù)最后你根本說不清性能提升是哪個改動帶來的。在這個基線配置下我記錄了開啟gst-shark之前的原始幀率管道總輸出統(tǒng)計約為18fps。接下來才打開trace工具定位問題。4.2 按順序讀指標(biāo)把懷疑范圍一步步縮小我先只開fps和proctimeexport GST_SHARK_CTF_DISABLE1 export GST_TRACERSfps;proctime deepstream-app -c config.txt終端輸出里能看到每個src pad的fps很快發(fā)現(xiàn)一個問題多個視頻源在app_src層面都是25fps穩(wěn)定輸入但sink側(cè)統(tǒng)計的fps只有18fps左右。這說明有數(shù)據(jù)在管道中途丟失或者處理不過來。接著看proctime列表我把各插件數(shù)據(jù)整理成一張表插件平均耗時標(biāo)準(zhǔn)差最大耗時nvv4l2decoder2.1 ms0.8 ms4.5 msnvstreammux0.9 ms0.2 ms1.8 msnvinfer21.4 ms6.3 ms38.2 msnvdsosd7.8 ms2.1 ms15.6 msnv3dsink1.1 ms0.3 ms2.9 msnvinfer的21.4ms平均耗時明顯異常。這個數(shù)字意味著單幀推理就占掉接近25ms按一幀33ms的預(yù)算幾乎沒有余量。而且最大耗時38.2ms說明在batch批量處理時某些批次的數(shù)據(jù)整合過程產(chǎn)生了額外的等待。為什么batch4的配置下nvinfer反而這么慢我打開buffer tracer繼續(xù)查export GST_TRACERSfps;proctime;buffer deepstream-app -c config.txt這次看到關(guān)鍵信息nvstreammux輸出到nvinfer的buffer大小是每批約24.8MB這個大小是4路1080p圖像數(shù)據(jù)的近似值。但nvinfer模型配置里的網(wǎng)絡(luò)輸入尺寸實際上是640x640x3。換句話說每一幀都要從1920x1080縮放到640x640而這個縮放動作發(fā)生在nvinfer內(nèi)部??s放本身在GPU上不算慢但如果每次推理前都要在GPU上搬運接近25MB的原始幀數(shù)據(jù)再加上縮放、顏色轉(zhuǎn)換、歸一化nvinfer這個插件實際承擔(dān)的工作遠(yuǎn)超“純推理”本身??吹竭@里懷疑范圍已經(jīng)收窄到nvinfer和它上游的數(shù)據(jù)流。第三步我繼續(xù)開queue-level tracer并且把注意力放在nvinfer下游是否有阻塞。結(jié)果顯示nvinfer下游的queue深度沒有持續(xù)增長反而是nvinfer上游也就是muxer到nvinfer之間沒有queue緩存壓力全部堆在muxer內(nèi)部。注意這個案例里的關(guān)鍵問題不是nvinfer本身而是輸入到nvinfer的數(shù)據(jù)規(guī)格和模型輸入規(guī)格不匹配導(dǎo)致插件內(nèi)部隱式承擔(dān)了大量圖像預(yù)處理工作。4.3 調(diào)整配置前后的效果對比根據(jù)上面定位的結(jié)果我做了一系列針對性調(diào)整而不是盲目提高batch-size或降低分辨率。第一步把nvinfer的模型輸入改為與預(yù)處理分辨率匹配的方式通過DeepStream配置中的net-scale-mode和預(yù)處理參數(shù)讓上游顯式完成縮放并盡量縮小實際進(jìn)入推理環(huán)節(jié)的數(shù)據(jù)量。第二步把batch-size從4提高到8。因為設(shè)備GPU內(nèi)存還有余量batch從4到8后推理時GPU的并行優(yōu)勢能發(fā)揮出來折算到每幀的平均推理耗時反而下降。第三步把推理精度從FP16切到INT8。模型此前已經(jīng)做過INT8校準(zhǔn)精度損失在可接受范圍內(nèi)但推理速度提升明顯。第四步處理nvdsosd的高耗時。7.8ms的OSD耗時明顯不正常檢查配置后發(fā)現(xiàn)啟用了基于CPU的OSD處理模式并把OSD處理模式改為GPU加速的配置。改動后OSD的proctime降低到1.2ms左右。調(diào)整后再次用同樣的trace命令跑同一組數(shù)據(jù)指標(biāo)優(yōu)化前優(yōu)化后總輸出幀率18.2 fps26.8 fpsnvinfer平均耗時21.4 ms9.6 msnvinfer最大耗時38.2 ms16.3 msnvdsosd平均耗時7.8 ms1.2 ms端到端延遲98 ms63 ms最終幀率從18fps提升到26.8fps超過了25fps的預(yù)期目標(biāo)。而且從最大耗時標(biāo)準(zhǔn)差來看抖動也收窄了不少視頻體驗更穩(wěn)。這個案例的整個排查過程本質(zhì)就是通過gst-shark把“感覺卡頓”轉(zhuǎn)化成“具體哪個插件耗時多少”的數(shù)字然后根據(jù)數(shù)字做決策的過程。5. 避坑記錄gst-shark使用中的常見問題和判斷方法5.1 工具自身開銷可能干擾數(shù)據(jù)讀數(shù)gst-shark會hook到GStreamer的buffer流動過程中每次buffer經(jīng)過時都要記錄時間、計算統(tǒng)計量這些操作本身會占用CPU。在嵌入式設(shè)備上如果CPU資源已經(jīng)非常緊張開啟tracer后幀率可能還會進(jìn)一步下降。這就帶來一個判斷難題你看到的降幀到底是原有瓶頸還是trace工具造成的我的處理方法是分場景看待。如果只是做短期現(xiàn)場調(diào)試接受10%以內(nèi)的性能下降是可以的主要看相對趨勢。如果是較長時間的穩(wěn)定性壓測就關(guān)閉trace只保留終端日志級別的fps統(tǒng)計。另外不要同時開很多tracer一個個開每次只增加一個維度這樣不僅能減少開銷還更容易定位是哪一層的問題。5.2 部分平臺讀不到GPU指標(biāo)時怎么辦gst-shark的gpu-usage tracer依賴GPU狀態(tài)獲取接口在部分嵌入式平臺或精簡版驅(qū)動環(huán)境中這個接口不可用顯示的值會一直是0或者直接報錯。遇到這種情況不要卡在這里。我的替代方案是用平臺的系統(tǒng)級工具單獨看GPU占用同時用gst-shark的proctime和buffer數(shù)據(jù)反推GPU側(cè)行為。比如nvinfer的proctime從6ms變成10ms同時系統(tǒng)級GPU工具顯示利用率上升那基本就能確認(rèn)推理耗時增加是真實存在的GPU計算量增加而不是CPU排隊假象。在只有CPU數(shù)據(jù)的情況下也有一招可以輔助判斷對比同一個插件處理不同分辨率輸入時的proctime變化。如果輸入分辨率翻倍proctime幾乎不變說明這個插件工作在批量并行模式下耗時和分辨率關(guān)系不大如果proctime隨輸入大小線性變化說明這個插件的處理邏輯沒有充分并行化值得深挖。5.3 多路流場景下統(tǒng)計口徑要統(tǒng)一多路視頻分析時gst-shark會把每個pad的數(shù)據(jù)分開統(tǒng)計也可能在終端里交替刷新。直接撈終端文本很容易看錯尤其是路數(shù)多的時候你分不清某個proctime到底屬于哪一路。我的做法是在文本輸出階段只關(guān)注要素級別的平均統(tǒng)計不細(xì)看單路。先判斷是否存在“某個插件普遍偏高”的情況如果所有路都偏慢問題出在公共環(huán)節(jié)比如nvinfer、nvstreammux如果只有一路偏慢問題出在這一路的私有環(huán)節(jié)比如該路的解碼器、該路的RTSP接收等。如果需要更精確的每路數(shù)據(jù)就導(dǎo)出CTF文件用帶界面的trace查看器分析那里面能單pad維度看詳細(xì)時間線。但導(dǎo)出CTF文件會顯著增加性能開銷建議在已經(jīng)縮小范圍后再做不要一開始就開著CTF跑長時間測試。5.4 版本不一致導(dǎo)致trace加載失敗我前面提到過GST_PLUGIN_PATH的問題。實際現(xiàn)場還會遇到另一種情況gst-shark編譯用的GStreamer版本和DeepStream內(nèi)置版本差異過大時即使路徑設(shè)置正確插件也可能加載失敗報“symbol lookup error”之類的錯。這個問題的排查思路是先確認(rèn)DeepStream自帶環(huán)境的版本用和它匹配的GStreamer頭文件重新編譯gst-shark。不要盲目用系統(tǒng)最新版穩(wěn)定比新功能更重要。裝好后再用gst-inspect-1.0快速驗證插件是否存在。5.5 性能問題不要只看插件本身最后想說一個容易被忽略的點。用gst-shark定位到某個插件proctime高不一定就是那個插件的算法低效也可能是它的輸入數(shù)據(jù)形態(tài)不好。比如某個convert插件耗時高未必是轉(zhuǎn)換函數(shù)寫得差而是上游給它送來的buffer本身跨內(nèi)存類型導(dǎo)致每幀都發(fā)生了GPU和CPU間的拷貝。遇到單插件耗時的異常我建議往上下游各看一步上游看它輸出buffer的類型、大小、速率下游看它對處理結(jié)果的消費節(jié)奏。只有把數(shù)據(jù)流整體看明白了才能判斷這個插件到底是“元兇”還是“背鍋俠”。在我做過的幾次DeepStream調(diào)優(yōu)中用不了一下午就能通過這套流程把問題壓縮到一兩個插件上。遇到卡頓別急著翻配置文檔先讓數(shù)據(jù)說話把插件效率攤開看優(yōu)化思路自然會清晰起來。