使用ARM NEON Intrinsics加速Video Codec

前言

最近公司在視頻直播項(xiàng)目中要使用H.265/HEVC蝴光,具體的是使用HW硬件編碼H.264/AVC禁筏,云端轉(zhuǎn)碼成H.265/HEVC并推流的解決方案激况。方案中使用的解碼器是FFMpeg中的H.265解碼器,該解碼器是從OpenHEVC直接獲取的玛歌,比起備受好評(píng)的H.264/AVC解碼器,這個(gè)解碼器目前優(yōu)化不足擎椰,在手機(jī)上占用資源較高支子。因此一個(gè)工作就是優(yōu)化該解碼器在手機(jī)上的性能表現(xiàn),主要使用ARM提供的SIMD指令進(jìn)行優(yōu)化达舒。

SIMD簡(jiǎn)介

Single Instruction Multiple Data (SIMD)值朋,單指令多數(shù)據(jù)。從字面理解巩搏,就是在CPU執(zhí)行中昨登,一條操作指令可以同時(shí)操作多個(gè)寄存器,從而在物理上倍數(shù)的加速運(yùn)行贯底。我理解范疇內(nèi)的X86平臺(tái)上最早的SIMD指令應(yīng)該是奔騰MMX上自帶的MMX指令丰辣,其寄存器寬度是64位,可以同時(shí)操作8個(gè)字節(jié)禽捆。MultiMedia eXtensions (MMX)是多媒體擴(kuò)展的意思笙什,其最初的設(shè)計(jì)目的就是為了加速圖像/視頻等高并行數(shù)據(jù)的處理速度。

  • 一個(gè)簡(jiǎn)單的SIMD示意圖如下所示:


    SIMD 8x8加法示意圖

在這里胚想,一條SIMD加法指令可以同時(shí)得到8個(gè)加法結(jié)果琐凭。就計(jì)算步驟本身而言,比單獨(dú)使用8條加法指令能夠獲得8倍的加速比浊服。從該示例也可以看出统屈,隨著寄存器長(zhǎng)度的變長(zhǎng),單指令能夠處理的數(shù)據(jù)量也越來(lái)越大牙躺,從而獲得更高的加速性能鸿吆。在Intel最新的AVX2指令集中,寄存器最大長(zhǎng)度已經(jīng)達(dá)到512位述呐。

ARM NEON Intrinsics簡(jiǎn)介

NEON指令是從Armv7架構(gòu)開(kāi)始引入的SIMD指令,其共有16個(gè)128位寄存器蕉毯。發(fā)展到最新的Arm64架構(gòu)乓搬,其寄存器數(shù)量增加到32個(gè)思犁,但是其長(zhǎng)度仍然為最大128位,因此操作上并沒(méi)有發(fā)生顯著的變化进肯。對(duì)于這樣的寄存器激蹲,因?yàn)榭梢酝瑫r(shí)存儲(chǔ)并處理多組數(shù)據(jù),稱之為向量寄存器江掩。Intrinsics是使用C語(yǔ)言的方式對(duì)NEON寄存器進(jìn)行操作学辱,因?yàn)橄啾扔趥鹘y(tǒng)的使用純匯編語(yǔ)言,具有可讀性強(qiáng)环形,開(kāi)發(fā)速度快等優(yōu)勢(shì)策泣。如果需要在代碼中調(diào)用NEON Intrinsics函數(shù),需要加入頭文件"arm_neon.h"抬吟。

數(shù)據(jù)類型

NEON Intrinsics內(nèi)置的整數(shù)數(shù)據(jù)類型主要包括以下幾種:

  • (u)int8x8_t;
  • (u)int8x16_t;
  • (u)int16x4_t;
  • (u)int16x8_t;
  • (u)int32x2_t;
  • (u)int32x4_t;
  • (u)int64x1_t;

其中萨咕,第一個(gè)數(shù)字代表的是數(shù)據(jù)類型寬度為8/16/32/64位,第二個(gè)數(shù)字代表的是一個(gè)寄存器中該類型數(shù)據(jù)的數(shù)量火本。如int16x8_t代表16位有符號(hào)數(shù)危队,寄存器中共有8個(gè)數(shù)據(jù)。

常用指令

NEON Intrinsics支持的所有指令可參看ARM NEON Intrinsics钙畔,其包含了常用的arm匯編指令類型茫陆,如數(shù)學(xué)運(yùn)算,邏輯運(yùn)算等擎析。另外簿盅,其引入了有針對(duì)性的加載/存儲(chǔ)/轉(zhuǎn)置/交叉存取等指令。部分常見(jiàn)的指令在會(huì)下面的示例環(huán)節(jié)中予以說(shuō)明叔锐。需要注意的是挪鹏,指令中的助記符與arm匯編是相同的。

示例1:
  • int16x8_t vqaddq_s16 (int16x8_t, int16x8_t)
  • int16x4_t vqadd_s16 (int16x4_t, int16x4_t)
  1. 第一個(gè)字母'v'指明是vector向量指令愉烙,也就是NEON指令讨盒;
  2. 第二個(gè)字母'q'指明是飽和指令,即后續(xù)的加法結(jié)果會(huì)自動(dòng)飽和步责;
  3. 第三個(gè)字段'add'指明是加法指令返顺;
  4. 第四個(gè)字段'q'指明操作寄存器寬度,為'q'時(shí)操作QWORD, 為128位蔓肯;未指明時(shí)操作寄存器為DWORD遂鹊,為64位;
  5. 第五個(gè)字段's16'指明操作的基本單元為有符號(hào)16位整數(shù)蔗包,其最大表示范圍為-32768 ~ 32767秉扑;
  6. 形參和返回值類型約定與C語(yǔ)言一致。

其它可能用到的助記符包括:

  • l 長(zhǎng)指令,數(shù)據(jù)擴(kuò)展
  • w 寬指令舟陆,數(shù)據(jù)對(duì)齊
  • n 窄指令, 數(shù)據(jù)壓縮
示例2
  • uint8x8_t vld1_u8 (const uint8_t *)
  1. 第二個(gè)字段'ld'表示加載指令
  2. 第三個(gè)字段'1'(注意是1误澳,不是l)表示順次加載。如果需要處理圖像的RGB分量秦躯,可能會(huì)用到vld3忆谓。關(guān)于vld/vst指令更詳細(xì)的說(shuō)明,請(qǐng)自己參閱arm官方文檔踱承。

函數(shù)改寫示例

1. 簡(jiǎn)單示例

原始代碼

// uint8_t *_dst, uint8_t *_src, int16_t *src2
// int height, int width
for (y = 0; y < height; y++) {
  for (x = 0; x < width; x++) {
    dst[x] = av_clip_pixel(((src[x] << 6) + src2[x] + offset) >> shift);
  }
  src  += srcstride;
  dst  += dststride;
  src2 += MAX_PB_SIZE;
}

改寫代碼

int16x8_t result_16x8;
int16x8_t offset_16x8 = vmovq_n_s16(offset);
int16x8_t minusshift_16x8 = vmovq_n_s16(-1 * shift);
int16x8_t min_16x8 = vmovq_n_s16(0);
int16x8_t max_16x8 = vmovq_n_s16(255);
        
for (y = 0; y < height; y++) {
  for (x = 0; x < width; x+=8) {
    result_16x8 = vshlq_n_s16(vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x]))), 6);
            result_16x8 = vshlq_s16(vqaddq_s16(vqaddq_s16(result_16x8, vld1q_s16(&src2[x])), offset_16x8), minusshift_16x8);
            vst1_u8(&dst[x], vqmovn_u16(vreinterpretq_u16_s16(vmaxq_s16(vminq_s16(result_16x8, max_16x8), min_16x8))));
  }
  src  += srcstride;
  dst  += dststride;
  src2 += MAX_PB_SIZE;
}

說(shuō)明:

  • 這里只針對(duì)寬度為8的倍數(shù)進(jìn)行了改寫倡缠,實(shí)際代碼中需要對(duì)傳入?yún)?shù)進(jìn)行判斷
  • vld1_u8讀取8字節(jié)數(shù)據(jù),vmovl_u8對(duì)讀取的uint8x8進(jìn)行寬度擴(kuò)展
  • vreinterpretq_s16_u16對(duì)數(shù)據(jù)類型進(jìn)行強(qiáng)制轉(zhuǎn)換
  • vshlq_n_s16對(duì)數(shù)據(jù)進(jìn)行左移處理(P.S. NEON提供了右移指令茎活,但是只能使用整數(shù)常量昙沦。需要根據(jù)變量進(jìn)行右移時(shí),只能使用左移負(fù)數(shù)位的方法妙色。)
  • vqmovn_u16對(duì)處理結(jié)果進(jìn)行寬度壓縮
  • vst1_u8將處理后的int16x8_t數(shù)據(jù)寫回內(nèi)存
2.進(jìn)階示例

原始代碼

/*
#define QPEL_FILTER(src, stride)   \
(filter[0] * src[x - 3 * stride] + \
 filter[1] * src[x - 2 * stride] + \
 filter[2] * src[x -     stride] + \
 filter[3] * src[x             ] + \
 filter[4] * src[x +     stride] + \
 filter[5] * src[x + 2 * stride] + \
 filter[6] * src[x + 3 * stride] + \
 filter[7] * src[x + 4 * stride])
 
DECLARE_ALIGNED(16, const int8_t, ff_hevc_qpel_filters[3][16]) = {
    { -1,  4,-10, 58, 17, -5,  1,  0, -1,  4,-10, 58, 17, -5,  1,  0},
    { -1,  4,-11, 40, 40,-11,  4, -1, -1,  4,-11, 40, 40,-11,  4, -1},
    {  0,  1, -5, 17, 58,-10,  4, -1,  0,  1, -5, 17, 58,-10,  4, -1}
};
*/
filter = ff_hevc_qpel_filters[mx - 1];
for (y = 0; y < height + QPEL_EXTRA; y++) {
  for (x = 0; x < width; x++)
    tmp[x] = QPEL_FILTER(src, 1);
  src += srcstride;
  tmp += MAX_PB_SIZE;
}

改寫代碼

/*
DECLARE_ALIGNED(16, const int8_t, ff_hevc_qpel_filtersT[3][64]) = {
    { -1, -1, -1, -1, -1, -1, -1, -1,  4,  4,  4,  4,  4,  4,  4,  4,//(0)
     -10,-10,-10,-10,-10,-10,-10,-10, 58, 58, 58, 58, 58, 58, 58, 58,
      17, 17, 17, 17, 17, 17, 17, 17, -5, -5, -5, -5, -5, -5, -5, -5,
       1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  0,  0,  0,  0},
    { -1, -1, -1, -1, -1, -1, -1, -1,  4,  4,  4,  4,  4,  4,  4,  4,//(1)
     -11,-11,-11,-11,-11,-11,-11,-11, 40, 40, 40, 40, 40, 40, 40, 40,
      40, 40, 40, 40, 40, 40, 40, 40,-11,-11,-11,-11,-11,-11,-11,-11,
       4,  4,  4,  4,  4,  4,  4,  4, -1, -1, -1, -1, -1, -1, -1, -1},
    {  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  1,  1,  1,//(2)
      -5, -5, -5, -5, -5, -5, -5, -5, 17, 17, 17, 17, 17, 17, 17, 17,
      58, 58, 58, 58, 58, 58, 58, 58,-10,-10,-10,-10,-10,-10,-10,-10,
       4,  4,  4,  4,  4,  4,  4,  4, -1, -1, -1, -1, -1, -1, -1, -1}
};
*/
int16x8_t filteT_16x8_0, filteT_16x8_1, filteT_16x8_2, filteT_16x8_3, filteT_16x8_4, filteT_16x8_5, filteT_16x8_6, filteT_16x8_7;
int16x8_t result_16x8;
      
filter = ff_hevc_qpel_filtersT[mx - 1];

filteT_16x8_0 = vmovl_s8(vld1_s8(&filter[0]));
filteT_16x8_1 = vmovl_s8(vld1_s8(&filter[8]));
filteT_16x8_2 = vmovl_s8(vld1_s8(&filter[16]));
filteT_16x8_3 = vmovl_s8(vld1_s8(&filter[24]));
filteT_16x8_4 = vmovl_s8(vld1_s8(&filter[32]));
filteT_16x8_5 = vmovl_s8(vld1_s8(&filter[40]));
filteT_16x8_6 = vmovl_s8(vld1_s8(&filter[48]));
filteT_16x8_7 = vmovl_s8(vld1_s8(&filter[56]));

for (y = 0; y < height + QPEL_EXTRA; y++) {
  for ( x = 0; x < width; x += 8 ) {
    // init the output reg
    result_16x8 = vmovq_n_s16(0);
    // (0)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x-3]))), filteT_16x8_0);
    // (1)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x-2]))), filteT_16x8_1);
    // (2)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x-1]))), filteT_16x8_2);
    // (3)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x]))), filteT_16x8_3);
    // (4)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x+1]))), filteT_16x8_4);
    // (5)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x+2]))), filteT_16x8_5);
    // (6)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x+3]))), filteT_16x8_6);
    // (7)
    result_16x8 = vmlaq_s16(result_16x8, vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x+4]))), filteT_16x8_7);
          
    // store the output data
    vst1q_s16(&tmp[x], result_16x8);
  }
  src += srcstride;
  tmp += MAX_PB_SIZE;
}

說(shuō)明:
在C實(shí)現(xiàn)中桅滋,每個(gè)結(jié)果需要讀取包括自身在內(nèi)的8個(gè)輸入,乘以相應(yīng)的系數(shù)并累加身辨。最簡(jiǎn)單直觀的實(shí)現(xiàn)方法是

output_16x8 = vmulq_s16( vreinterpretq_s16_u16(vmovl_u8(vld1_u8(&src[x-3]))), vmovl_s8(vld1_s8(ff_hevc_qpel_filters[mx - 1])));

這樣實(shí)現(xiàn)丐谋,會(huì)使得8個(gè)乘積分布在同一個(gè)向量寄存器中,需要通過(guò)取寄存器的不同元素實(shí)現(xiàn)累加煌珊,加法部分無(wú)法并行号俐。
在C實(shí)現(xiàn)中,其數(shù)學(xué)表示為兩個(gè)1x8和8x1的矩陣之間的乘法定庵。分析數(shù)據(jù)間的關(guān)系吏饿,將矩陣乘法轉(zhuǎn)換為矩陣轉(zhuǎn)置乘法,可以得出前文改寫代碼的實(shí)現(xiàn)蔬浙。在該實(shí)現(xiàn)中猪落,由于濾波器系統(tǒng)固定,因此預(yù)先定義了其轉(zhuǎn)置矩陣并擴(kuò)展畴博。在進(jìn)行'乘加'操作的過(guò)程中笨忌,一個(gè)循環(huán)將8個(gè)結(jié)果全部計(jì)算完畢,使得乘法/加法均實(shí)現(xiàn)了并行化俱病。
P.S. 這里官疲,單獨(dú)設(shè)置了8個(gè)向量寄存器變量并展開(kāi)使得代碼較長(zhǎng),使用循環(huán)+數(shù)組的方式也可以得到同樣的結(jié)果亮隙,且代碼較短途凫。但是在底層高頻函數(shù)中,盡量展開(kāi)循環(huán)可以最大化的提升效率溢吻。

結(jié)語(yǔ)

本文只介紹了使用ARM NEON Intrinsics的原理和基本應(yīng)用维费。實(shí)際中需要對(duì)待優(yōu)化的函數(shù)原理及能使用的資源了解清楚才能使用最有效的方法并行化程序。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市犀盟,隨后出現(xiàn)的幾起案子噪漾,更是在濱河造成了極大的恐慌,老刑警劉巖且蓬,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異题翰,居然都是意外死亡恶阴,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門豹障,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冯事,“玉大人,你說(shuō)我怎么就攤上這事血公£墙觯” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵累魔,是天一觀的道長(zhǎng)摔笤。 經(jīng)常有香客問(wèn)我,道長(zhǎng)垦写,這世上最難降的妖魔是什么吕世? 我笑而不...
    開(kāi)封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮梯投,結(jié)果婚禮上命辖,老公的妹妹穿的比我還像新娘。我一直安慰自己分蓖,他們只是感情好尔艇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著么鹤,像睡著了一般终娃。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上午磁,一...
    開(kāi)封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天尝抖,我揣著相機(jī)與錄音,去河邊找鬼迅皇。 笑死昧辽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的登颓。 我是一名探鬼主播搅荞,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了咕痛?” 一聲冷哼從身側(cè)響起痢甘,我...
    開(kāi)封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茉贡,沒(méi)想到半個(gè)月后塞栅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腔丧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年放椰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉粤。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡砾医,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出衣厘,到底是詐尸還是另有隱情如蚜,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布影暴,位于F島的核電站错邦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坤检。R本人自食惡果不足惜兴猩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望早歇。 院中可真熱鬧倾芝,春花似錦、人聲如沸箭跳。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谱姓。三九已至借尿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屉来,已是汗流浹背路翻。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茄靠,地道東北人茂契。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像慨绳,于是被迫代替她去往敵國(guó)和親掉冶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子真竖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容