h.264並行熵解碼

 

在前面討論並行解碼的章節中,咱們專一於討論解碼的宏塊重建部分,甚至把宏塊重建描述成宏塊解碼,這是由於在解碼工做中,宏塊重建確實佔了至關大的比重,不過解碼還包含其它的部分,按照解碼流程可粗略分爲:html

  1. 讀取並初步解析碼流(front-end)
  2. 熵解碼(本文章只討論CABAC)
  3. 幀間預測、幀內預測 (主要討論部分爲運動向量預測)
  4. 宏塊重建

image

在之前的並行解碼文章,咱們主要討論了宏塊重建的並行算法,得知採用不一樣的算法,會產生不一樣的並行度。在不考慮硬件負擔的狀況下,並行度能夠達到幾十,甚至上千,如此一來,除開宏塊重建的其它解碼模塊就頗有可能成爲解碼的瓶頸。所以,得到各解碼流程在解碼中所佔的比例,並提升瓶頸部分的執行效率是接下來須要解決的重點問題。算法

 

阿姆達爾定律(Amdahl's law)

在進行實際的工做以前,先來分析一下瓶頸對於並行系統性能的影響。阿姆達爾定律能充分闡述這一影響的。緩存

阿姆達爾定律聲明瞭一個並行處理系統,系統的流程包含了非並行部分,所佔比例爲整個系統的s,系統的流程中的可並行部分所佔比例爲整個系統的1-s,p爲該並行系統的核心數,那麼這個並行系統有最大性能提高爲負載均衡

$ S_{max} = \frac{1}{s+\frac{1-s}{p}} $ide

本來在單位時間1能夠完成1項做業,在通過並行處理後最高能夠完成$S_{max}$項做業性能

image

不過因爲系統中存在不可並行部分s,因此即便$p \to \infty $,最大性能$S_{max}$也只能到$\frac{1}{s}$,s就是該系統的瓶頸。爲了更顯著提升系統性能,須要想辦法提升s部分的效率。測試

以解碼系統來講,假設宏塊重建部分佔比爲80%,核心數爲16,那麼解碼系統並行解碼的性能爲優化

$S_{max} = \frac{1}{0.2+\frac{0.8}{16}} = 4$this

並行度只有4,最大也不超過5,與核心數有明顯的誤差。所以提高解碼其它部分的效率就顯得很是重要。編碼

 

各解碼模塊在串行解碼流程中的佔比

佔比,即各模塊運行所用時間佔整體解碼時間的比例。在串行(順序)解碼器中,時間能表明某一模塊佔用cpu資源的多少,這偏偏是咱們最關心的。

串行解碼準備

爲了得到各模塊的時間佔比,首先須要一個串行解碼器,本文采用ffmpeg。而後在解碼器的四個模塊的起止處記錄時間,所採集到的時間在解碼結束後會被用於統計得出各模塊在解碼過程當中的平均時間佔比。

接下來是選取要解碼的h.264碼流。爲了使得出來的結論更具備通常性,所選取的碼流必須多種多樣。例如不一樣畫面變化程度的碼流、不一樣分辨率的碼流、不一樣壓縮質量的碼流,在實驗中爲了作到變量的可控,通常會選取不一樣的視頻序列用x264進行壓縮從而獲得所需碼流。《Scalable Parallel Programming Applied to H.264/AVC Decoding》一書中選取了如下條件的碼流進行測試。

Video name Resolution [pixels] Frame rate Bitrate[Mbps]
crf22 crf27 crf32 crf37 intra
BlueSky+Pedestrain+Riverbed 1920x1080 25 12.2 6.17 3.28 1.72 51.2
CrowdRun+ParkJoy 3840x2160 50 109 48.3 23.6 12.0 405

第一組爲1080p分辨率的三個視頻序列的組合,第二組爲2160p分辨率的兩個視頻序列的組合,把畫面變化不一樣的視頻序列組合到一塊兒,在某種程度上能體現出視頻場景的通常性。

這兩個組合後的視頻序列會分別用x264進行編碼,獲得咱們所需的碼流。編碼時採用CRF(Constant Rate Factor)來對碼流質量進行控制,數字越小表明質量越高,所以編碼後獲得的碼率也會越高。另外,在只採用intra模式進行編碼的狀況下,碼流的碼率是其中最高的,1080p25就高達50Mbps。

 

統計串行解碼結果

對上述碼流用ffmpeg進行解碼後統計出來的結果以下圖

image

image

從圖中能總結出如下結論

  1. CABAC解碼、宏塊重建、運動向量預測平均消耗了總解碼時間的99.5%
  2. CABAC解碼時間佔比受編碼選項的影響很大,佔比從crf37的20%到intra-only的72%不等,平均值爲37.6%
  3. 各解碼模塊的解碼時間佔比不是固定的,編碼選項是影響各模塊時間佔比的主要緣由

須要注意的一點是,這裏的解碼採用的ffmpeg的宏塊重建模塊是通過了SIMD指令優化的,而熵解碼(CABAC)模塊沒有能有效進行SIMD指令優化的數據。

在得到平均熵解碼耗時佔比後,就能經過對阿姆達爾定律稍做修改,獲得性能提高的目標值。

$S_{max} = \frac{1}{\frac{s}{f}+\frac{1-s}{p}}$

其中,$S_{max}$爲性能提高的目標倍數,s爲熵解碼的耗時佔比,1-s爲宏塊重建耗時佔比(Front End因爲佔比過小忽略不計,運動向量預測佔比也比較小,並且後面會討論到該模塊合併到熵解碼或者宏塊重建模塊中,所以在此忽略),f爲熵解碼加速倍率,p爲並行核心數。

前面已獲得熵解碼的平均耗時佔比爲37.6%,這裏取值s=0.38。不過須要注意,若是熵解碼是採用硬件進行性能提高的化,取平均值並不合適,而是應該考慮最壞的狀況。假設爲64核系統,那麼按照《Scalable…》書中關於3D-Wave算法中的討論,宏塊重建達到的性能提高倍率爲50,咱們這裏的目標是達到其80%,即

$0.8 \times 50 = \frac{1}{\frac{0.38}{f} + \frac{0.62}{64} }$

獲得f = 24.8,也就是說,熵解碼要提高到24.8倍的速度,可是目前來講即便用硬件來實現CABAC解碼模塊也沒法達到這個目標,所以咱們須要考慮並行實現。

 

並行CABAC熵解碼

數據分割

咱們之前分析過CABAC,它是以slice爲一個編碼週期,在同一週期內,編碼過程具備極高的相關性,所以是沒法在slice如下級別進行並行的。不過,在slice與frame級別中能夠很輕鬆地進行數據分割,分割獲得的是互不相干的數據,而後便可進行並行熵解碼。

在討論h.264語法結構的時候,咱們知道h.264規定了幀的起始標記爲0x00000001,slice的起始標記爲0x000001。那麼在把碼流從磁盤讀取進內存時,若是發現起始標記,便可把接下來的碼流分配給熵編碼線程進行解碼,直到碰到下一個起始標記,再把接下來的碼流分配各另外一個熵解碼線程,如此一來就能夠實現並行熵解碼。

 

假依賴(false dependency)

通常來講,並行系統的設計是在原有的串行系統(legacy code)的基礎上修改而來的,也就是說須要把上述的這種並行方法集成到現有的串行h.264解碼器上,如ffmpeg。ffmpeg是通過良好優化的解碼器,它爲了提升cache的命中率,把運動向量部分包含在CABAC解碼模塊內,如此一來宏塊在熵解碼完成後就能當即進行預測,沒必要重複加載宏塊信息,所以能提高解碼效率。不過這種作法在並行熵解碼上會帶來一些問題。

運動向量預測當中有一種預測模式稱爲B幀直接預測(B Direct),這種模式只存在於B幀當中。採用了這種模式的宏塊在碼流中並無本身的運動向量,而是重用了最近參考幀的co-locate宏塊的運動向量,這是一種屬於運動向量預測模塊的依賴。在編碼運動較單一的視頻中,這種模式能有效提升壓縮率。而在解碼端,因爲ffmpeg中的運動向量預測與CABAC熵解碼耦合度很高,那麼在並行化的過程當中就會引入了本來不屬於CABAC的依賴,這被稱爲假依賴(Therefore, from the point of view of CABAC stage, this dependency is a false dependency and is due to legacy code)。

這種假依賴意味着CABAC線程在解碼B Direct宏塊時就必須先阻塞,等待co-locate所在宏塊解碼完成才能繼續解碼。若是咱們沒有相應的對策,線程就有可能由於這種假依賴的引入的阻塞影響到解碼效率。

image

優化假依賴的策略

針對這種會影響到並行解碼效率的狀況,有如下幾種對策

  • 用熵解碼宏塊進度表來控制熵解碼順序,保證當前幀的熵解碼永遠比前一幀慢至少一個宏塊。如下是一個稱爲Entropy Ring (ER)的策略,該策略主要包含兩類線程:一個Dist線程用於分發熵解碼任務,n個Entropy Decoding Threads (EDT)用於進行熵解碼。策略規定了每一個EDT解碼固定的某幀,即EDTi解碼frame i+n, i+2n……,造成解碼環,所以稱爲Ring。EDTi在解碼完成一個宏塊後須要更新宏塊進度表,而EDTi+1須要根據這個表了決定是否開始進行下一個宏塊的熵解碼。

    EDT 1 2 3 n
    ED MB a b c d

    宏塊進度表

    ER_small

    *宏塊進度表用於記錄線程的熵解碼進度,單位爲宏塊。

     

  • B-Ring。這一策略是ER的改進版本。在通常的視頻中,B幀佔絕大多數,熵解碼時間較短,而I、P幀佔比例小,熵解碼時間較長,而且I、P幀中因爲不含有B Direct模式,所以能夠隨意進行熵解碼而不用擔憂假依賴問題。出於負載均衡的考慮,熵解碼線程應該分紅兩大組,一是解碼I、P幀的線程組IP,另外一組是解碼B幀的線程組B。B幀仍須要按照ER那樣進行Ring分配,IP則因爲不存在假依賴關係,所以不用遵循特定解碼順序。

    EDT IP1 IP2 B1
    ED MB a b c

    宏塊進度表

    BR_small

    上述兩種策略更詳細請參考A QHD-Capable Parallel H.264 Decoder

  • 將運動向量預測部分從CABAC解碼分離出來,合併到宏塊重建部分。B Direct所引入的依賴是最近參考幀上與當前宏塊相同位置上的宏塊,也就是幀間依賴。在static 3D-Wave算法中,該依賴是涵蓋在了宏塊重建的幀間依賴範圍內的,而在Dynamic 3D-Wave中,咱們能夠在宏塊重建開始前把該依賴添加到幀間依賴表中去。固然,分離耦合度很高的兩部分須要對legacy code有足夠的理解,做業的工程量並不小。另外,這麼作破壞了legacy code本來的提高代碼命中率的目的,所以在串行解碼的時候效率會比原來有所下降。

 

熵解碼與宏塊重建的解耦

熵解碼與宏塊重建分別做爲獨立的大模塊進行並行解碼,兩個模塊之間必然會有大量數據往來,其中最顯著的數據的就是語法元素(syntax element)。熵解碼獲得的語法元素會做爲宏塊重建的原料。爲了維持雙方的高並行,中間須要大量的內存空間來緩存語法元素。

 

實驗與結論

如前文所述,書中採用的是基於ffmpeg修改的並行解碼器,這裏爲了單獨測試熵解碼的並行解碼性能停用了宏塊重建模塊,具體實驗設置與結果能夠移步書中去查看。這裏扯一下結論:串行時CABAC熵解碼所用的時間越長,高並行時性能提高越明顯。

image

如上圖的例子是1080p25的結果,intra only在40核並行時仍然能保持35以上的倍率;crf27在40核並行時只有21的倍率;crf32在40核並行時更是隻有15的倍率。這實際上是內存帶寬形成的影響,因爲內存帶寬的限制致使倍率再也不提高。

咱們回顧串行解碼時的數據,能夠獲得串行解碼不一樣配置的碼流的平均熵解碼時間,在這次實驗中能夠獲得並行熵解碼不一樣配置的碼流的平均熵解碼時間,對比獲得以下表格。

Preset intra only crf27 crf32
Seiral 43ms 6.4ms 4ms
Parallel 1.2ms 0.3ms 0.27ms
Boost 35 21 15

*上述數據爲按圖目測,不盡精確

熵解碼吞吐的數據中包含有輸入的碼流以及解碼後的語法元素,而碼流畢竟是通過壓縮的數據,相比語法元素所佔用的內存會小不少,所以咱們能夠將內存吞吐的數據看做約一幀的語法元素。咱們對比的是同一視頻序列,儘管該視頻序列以不一樣的配置進行壓縮獲得不一樣碼流,可是碼流在解碼後獲得的語法元素就是內存中的某個變量,變量都是有固定格式、固定大小的。所以能夠認爲不一樣碼流的語法元素佔用內存大小的區別不是很大。也就是說,能夠粗略認爲intra only用1.2ms吞吐了一幀語法元素,此時並無達到內存帶寬瓶頸,而crf32用0.27m吞吐一幀語法元素,此時已經達到了內存帶寬的瓶頸。

*上述結論創建在假設系統的磁盤與內存之間存在出色的緩存機制,咱們只需關注內存與cpu間的數據傳輸問題

相關文章
相關標籤/搜索