組み込み関数(intrinsic)によるsimd入門

122
関数(intrinsic) いた 関数(intrinsic) いた SIMD化による高速画像処理入門 名古屋工業大学 福嶋慶繁 名古屋工業大学 福嶋慶繁 Twitter @fukushima1981 サンプルコードはこちら https://github.com/norishigefukushima/IntrinsicsSample

Upload: norishige-fukushima

Post on 28-May-2015

19.232 views

Category:

Technology


9 download

TRANSCRIPT

Page 1: 組み込み関数(intrinsic)によるSIMD入門

組み込み関数(intrinsic)を用いた組み込み関数(intrinsic) を用いたSIMD化による高速画像処理入門

名古屋工業大学 福嶋慶繁名古屋工業大学 福嶋慶繁

Twitter @fukushima1981

サンプルコードはこちらhttps://github.com/norishigefukushima/IntrinsicsSample

Page 2: 組み込み関数(intrinsic)によるSIMD入門

整数の足し算整数の足し算

Page 3: 組み込み関数(intrinsic)によるSIMD入門

2進数表現2進数表現

Page 4: 組み込み関数(intrinsic)によるSIMD入門

実際は?(shortの例)実際は?(shortの例)

0000000000000011++

00000000000001000000000000000100 = ?

Page 5: 組み込み関数(intrinsic)によるSIMD入門

実際は?(shortの例)実際は?(shortの例)

0000000000000011++

00000000000001000000000000000100 = ?

もったいなくない?もったいなくない?

Page 6: 組み込み関数(intrinsic)によるSIMD入門

目標目標

OpenCVによりも高速なプログラムを簡OpenCVによりも高速なプログラムを簡単につくれる書き方を覚えよう単 くれる書き方を覚えよう出来るようになること・高速コピー src.copyTo(dest);高速コピ src.copyTo(dest);・高速な加減乗算 dest = src1 + src2;・高速型変換 src.convert(dest,CV_16U);・高速BGR2RGB cvtColor(src,dest,CV_BGR2RGB);・高速BGR2Gray cvtColor(src,dest,CV_BGR2Gray);

※出来ないこと

整数演算だけ 浮動小数点の命令は使わない 比較演算は使整数演算だけ,浮動小数点の命令は使わない,比較演算は使わない,水平演算は使わない,AVXは含まない(SSE4.1まで)

Page 7: 組み込み関数(intrinsic)によるSIMD入門

目的目的

を使えば 化は難しくないよ• Intrinsicを使えばSIMD化は難しくないよ

でもOpenCVはちょくちょく関数のSIMD化アップデート– OpenCVはちょくちょく関数のSIMD化アップデ トしてるよ

• Changeset [7055]: optimized cmp and cvtscale(16s‐Changeset [7055]: optimized cmp and cvtscale(16s>16s) using SSE2 (thanks to Victoria): optimized cmpand cvtsc…

– コアな処理の関数ならIPP使えばAVXまで使った適化してあるよ(Linuxなら研究用は無料)

http://software.intel.com/en‐us/non‐commercial‐software‐development

Page 8: 組み込み関数(intrinsic)によるSIMD入門

MenuMenu

ピピコピー a.copyTo(b)変換 a.convertTo(b,CV 16S)コピー a.copyTo(b)変換 a.convertTo(b,CV 16S)変換 a.convertTo(b,CV_16S)加算 c = a + b, cv::add(a,b,c)乗算 l(b) lti l ( b )

変換 a.convertTo(b,CV_16S)加算 c = a + b, cv::add(a,b,c)乗算 l(b) lti l ( b )乗算 c = a.mul(b) , cv::multiply(a,b,c)変換2 a.convertTo(b,CV_16S,5,10)乗算 c = a.mul(b) , cv::multiply(a,b,c)変換2 a.convertTo(b,CV_16S,5,10)( , _ , , )反転 cv::flip(a,b,‐1)色変換1 cvtColor(a b CV BGR2RGB)

( , _ , , )反転 cv::flip(a,b,‐1)色変換1 cvtColor(a b CV BGR2RGB)色変換1 cvtColor(a,b,CV_BGR2RGB)分離 cv::slipt(a,v)色変換1 cvtColor(a,b,CV_BGR2RGB)分離 cv::slipt(a,v)色変換2 cvtColor(a,b,CV_BGR2Gray)色変換2 cvtColor(a,b,CV_BGR2Gray)

Page 9: 組み込み関数(intrinsic)によるSIMD入門

先に結果先に結果

ピ 効果なしピ 効果なしコピー 効果なし変換 1.5倍コピー 効果なし変換 1.5倍変換 1.5倍加算 1.4倍乗算 4 0倍

変換 1.5倍加算 1.4倍乗算 4 0倍乗算 4.0倍変換2 9.8倍乗算 4.0倍変換2 9.8倍反転 7.0倍色変換1 5 7倍反転 7.0倍色変換1 5 7倍色変換1 5.7倍分離 3.8倍色変換1 5.7倍分離 3.8倍色変換2 4.5倍色変換2 4.5倍

Page 10: 組み込み関数(intrinsic)によるSIMD入門

通常のプログラミングでは,意識しないこと

レジスタレジスタ一番CPUに近い記憶装置番CPUに近い記憶装置ハード

ディスクメモリ

L2

キャッシュ

L1

キャッシュレジスタ

演算回路(+ー×÷)

SIMD演算ではもったいない部分を有効活用できるレジスタを使う

m128__m128128bitを記憶できるレジスタ(SSE2の場合)

Page 11: 組み込み関数(intrinsic)によるSIMD入門

アセンブラアセンブラ

プ ジ 開始mul_asm proc                    ; プロシジャ開始 ;push    ebp ; 引数処理:後述 ;mov ebp, esp

di [ b 8]mov edi, [ebp + 8]mov esi, [ebp + 12] ; ここまで引数処理 ;mov ecx, 32

LL01LL01:movaps xmm0, [edi]mulps xmm0, [esi]movaps [edi] xmm0movaps [edi], xmm0add     edi, 16add     esi, 16loop LL01loop    LL01pop     ebp ; 引数処理:後述 ;ret

mul asm endp ;プロシジャ終了 ;mul_asm endp ; プ シジャ終了 ;

http://www1.icnet.ne.jp/nsystem/simd_tobira/simd_cording.html

Page 12: 組み込み関数(intrinsic)によるSIMD入門

アセンブラアセンブラ

プ ジ 開始mul_asm proc                    ; プロシジャ開始 ;push    ebp ; 引数処理:後述 ;mov ebp, esp

di [ b 8]よっぽど訓練されmov edi, [ebp + 8]mov esi, [ebp + 12] ; ここまで引数処理 ;mov ecx, 32

LL01

よっぽど訓練されないと読めない て

LL01:movaps xmm0, [edi]mulps xmm0, [esi]movaps [edi] xmm0

ないと読めないってmovaps [edi], xmm0add     edi, 16add     esi, 16loop LL01loop    LL01pop     ebp ; 引数処理:後述 ;ret

mul asm endp ;プロシジャ終了 ;mul_asm endp ; プ シジャ終了 ;

http://www1.icnet.ne.jp/nsystem/simd_tobira/simd_cording.html

Page 13: 組み込み関数(intrinsic)によるSIMD入門

Intrinsic関数Intrinsic関数

{• __asm { mov ch1, 0x41mov ch2, 0x42 }, }– とか魔法を唱えなくてもOK

言語 関数 た 使 る• C言語の関数みたいに使える– 例:ucharが16個のデータ”a”をレジスタ”ra”にロードuchar a[16];uchar a[16];__m128i ra = _mm_load_si128((__m128i*)(a));

注意:自分でアセンブラを書いたほうが 適コードを書くことが出来ます.

Page 14: 組み込み関数(intrinsic)によるSIMD入門

目次目次

基本編 O CV編• 基本編– __m128– ロード(load),ストア(store),

整数の加算

• vs OpenCV編– コピー

• a.copyTo(b)型変換整数の加算

– アライメントについて

• 応用編

– 型変換• a.convertTo(b,CV_16S)

– 加算( )– 型変換

• Pack• Unpack

• add(a,b,c);– 乗算

• multiply(a,b,c)色のスワ プ• cvt

– シャッフル– Stream書き込み

– 色のスワップ• cvtColor(a,b,CV_BGR2RGB)

– 色の分離li ( b)• slipt(a,b);

– グレイスケール変換• cvtColor(a,b,BGR2Gray)

Page 15: 組み込み関数(intrinsic)によるSIMD入門

基本編

メモリからレジスタにロードメモリからレジスタにロ ド

レジスタ同士のSIMD演算

レジスタからメモリにストア

Page 16: 組み込み関数(intrinsic)によるSIMD入門

m128__m128

b のレジスタを自由に切 て使う• 128 bitのレジスタを自由に切って使う– 整数 (__m128i)

• 8bit(char,uchar) x 16個• 16bit(short) x 8個• 32bit(int) x 4個• 64 bit(long) x2

小数( )– 小数(__m128, __m128d)• 16bit(float)• 32bit(double)• 64bit(long double)

Page 17: 組み込み関数(intrinsic)によるSIMD入門

レジスタ( m128)レジスタ(__m128)128 bitのレジスタを自由に切って使う128 bitのレジスタを自由に切って使う

128bit x 1long double

64bit x 2long , double

32bit x 4int, float

16bit x 8short

8bit x 16char

Page 18: 組み込み関数(intrinsic)によるSIMD入門

SIMD演算SIMD演算

“1回の命令”で16個のデータを同時に足し算する例1回の命令 で16個のデータを同時に足し算する例

8bit x 16char

8bit x 16

+ + + + + + + + + + + + + + + +

8bit x 16char

8bit x 16charchar

Page 19: 組み込み関数(intrinsic)によるSIMD入門

unsigned charの加算のコードunsigned charの加算のコード

void loadaddstoreBF(uchar* src1, uchar* src2, uchar* dest, const int size)

{{for(int i=0;i<size;i++){{

dest[i] = src1[i]+src2[i];}

}

ただの足し算

Page 20: 組み込み関数(intrinsic)によるSIMD入門

Intrinsicによる加算コードの 適化Intrinsicによる加算コードの 適化

id l d dd t SSEvoid loadaddstoreSSE(uchar* src1, uchar* src2, uchar* dest, const int size)

{{for(int i=0;i<size;i+=16){

//データをレジスタa,bに128bit(8bit16個)ロードする__m128i a = _mm_load_si128((__m128i*)(src1+i));

128i b l d i128(( 128i*)( 2 i))__m128i b = _mm_load_si128((__m128i*)(src2+i));//aとbのデータを同時に16個足し算し,aに結果を書きこむ

a = mm add epi8(a b);a   _mm_add_epi8(a,b);//レジスタaのデータをメモリのdestに128bit(8bit16個)ストアする

_mm_store_si128((__m128i*)(dest+i),a);}

}

Page 21: 組み込み関数(intrinsic)によるSIMD入門

実験結果(uchar)実験結果(uchar)

大きな配列(5Gb t )を入力して計算大きな配列(5Gbyte)を入力して計算

66 ms (SSE)( )vs

770 ms (ベタ)

11 6倍の高速化11.6倍の高速化

Page 22: 組み込み関数(intrinsic)によるSIMD入門

shortの加算のコードshortの加算のコード

void loadaddstoreBF(short* src1, short* src2, short* dest, const int size)

{{for(int i=0;i<size;i++){{

dest[i] = src1[i]+src2[i];}

}

shortの足し算

Page 23: 組み込み関数(intrinsic)によるSIMD入門

Intrinsicによる加算コードの 適化Intrinsicによる加算コードの 適化

id l d dd t SSEvoid loadaddstoreSSE(short* src1, short* src2, short* dest, const int size)

{{for(int i=0;i<size;i+=8){

//データをレジスタa,bに128bit(16bit8個)ロードする__m128i a = _mm_load_si128((__m128i*)(src1+i));

128i b l d i128(( 128i*)( 2 i))__m128i b = _mm_load_si128((__m128i*)(src2+i));//aとbのデータを同時に8個足し算し,aに結果を書きこむ

a = mm add epi16(a b);a   _mm_add_epi16(a,b);//レジスタaのデータをメモリのdestに128bit(16bit8個)ストアする

_mm_store_si128((__m128i*)(dest+i),a);}

}

Page 24: 組み込み関数(intrinsic)によるSIMD入門

実験結果(short)実験結果(short)

大きな配列(5Gb t )を入力して計算大きな配列(5Gbyte)を入力して計算

120 ms (SSE)( )vs

770 ms (ベタ)

6 4倍の高速化6.4倍の高速化

Page 25: 組み込み関数(intrinsic)によるSIMD入門

ロードとストア(SSE2)ロードとストア(SSE2)

から ジ タ 転送Load(メモリsrcからレジスタaへ転送)__m128i a = _mm_load_si128((__m128i*)(src));

Store(レジスタaからメモリdestにコピー)Store(レジスタaからメモリdestにコピ )

_mm_store_si128((__m128i*)(dest),a);

メモリの型に関係なく,128bitのデータをメモリの型に関係なく,128bitのデ タをメモリーレジスタ間で転送する関数

Page 26: 組み込み関数(intrinsic)によるSIMD入門

Loadのデータの並び順Loadのデータの並び順

{ }をロData ={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}をロード

dataの型の型がcharのとき

0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15shortのときshortのとき

0,0,1,0,2,0,3,0,4,0,5,0,6,0,7,0のときintのとき

0,0,0,0,1,0,0,0,2,0,0,0,3,0,0,0

Page 27: 組み込み関数(intrinsic)によるSIMD入門

加算 mm add epi(SSE2)加算 _mm_add_epi(SSE2)

レジスタ bをSIMDで加算する関数レジスタa,bをSIMDで加算する関数

a = _mm_add_epi8(a,b);//uchar 16個dd ( b) // h 個a = _mm_add_epi16(a,b);//short 16個

a = _mm_add_epi32(a,b);//int 16個

原理上,Ucharは16倍速,shortは8倍速,intは4倍速で加算が出来る算が出来る

その他:

dd i8 飽和演算付き i d足し算_mm_adds_epi8 飽和演算付きsigned足し算

_mm_adds_epu8  飽和演算付きunsigned 足し算

Page 28: 組み込み関数(intrinsic)によるSIMD入門

他にも演算はたくさんある他にも演算はたくさんある

Add 加算Add 加算Adds 飽和演算付き加算

減算Sub 減算Subs 飽和演算付き減算Avg 平均Min 小値Max 大値Mul 乗算乗算Sad 絶対誤差|a‐b|

Page 29: 組み込み関数(intrinsic)によるSIMD入門

アライメントアライメント

128i l d i128(( 128i*)( ))__m128i a = _mm_load_si128((__m128i*)(src));_mm_store_si128((__m128i*)(dest),a);メモリの番地が16の倍数である必要がある※そろってないとプログラムが強制終了orz

こんな感じでメモリ確保をすればOKこんな感じでメモリ確保をすればOK宣言

_declspec( align(16) ) uchar data[size];動的確保

_aligned_malloc(size_t size, 16) (VCの場合)aligned free(void *memblock) (VCの場合)_aligned_free(void  memblock) (VCの場合)

※OpenCVのMatで中身をつくるとアライメントがそろってます

Page 30: 組み込み関数(intrinsic)によるSIMD入門

アライメントによるの自由度の損失アライメントによるの自由度の損失

__m128i a = _mm_load_si128((__m128i*)(src));

_mm_store_si128((__m128i*)(dest),a);

src+=16,dest+=16など16区切りでしか処理できない...

メモリの区切りをずらした処理が出来ない...

Page 31: 組み込み関数(intrinsic)によるSIMD入門

アライメントがずれる場合アライメントがずれる場合

void loadaddstoreSSE(uchar* src1 uchar* src2 uchar* dest const int size)(uchar  src1, uchar  src2, uchar  dest, const int size)

{for(int i=0;i<size;i+=16){

__m128i a = _mm_load_si128((__m128i*)(src1+i));//1画素ずらして足し算したい//1画素ずらして足し算したい

__m128i b = _mm_load_si128((__m128i*)(src2+i+1));a = mm add epi8(a,b);a   _mm_add_epi8(a,b);_mm_store_si128((__m128i*)(dest+i),a);

}} この関数は落ちる

Page 32: 組み込み関数(intrinsic)によるSIMD入門

アライメントがずれる場合アライメントがずれる場合

void loadaddstoreSSE(uchar* src1 uchar* src2 uchar* dest const int size)(uchar  src1, uchar  src2, uchar  dest, const int size)

{for(int i=0;i<size;i+=16){

__m128i a = _mm_loadu_si128((__m128i*)(src1+i));//1画素ずらして足し算したい//1画素ずらして足し算したい

__m128i b = _mm_loadu_si128((__m128i*)(src2+i+1));a = mm add epi8(a,b);a   _mm_add_epi8(a,b);_mm_storeu_si128((__m128i*)(dest+i),a);

}} この関数は落ちない(すこし遅い)

Page 33: 組み込み関数(intrinsic)によるSIMD入門

応用編

型変換型変換

シャッフル

Stream書き込み

Page 34: 組み込み関数(intrinsic)によるSIMD入門

実際に画像処理をするときは実際に画像処理をするときは...

ジ タ ド• メモリからレジスタにロード(8bit)

• レジスタの型変換レジスタの型変換

• レジスタ同士のSIMD演算(16bit)

• レジスタの型変換

• レジスタからメモリにストア(8bit)レジスタからメモリにストア(8bit)

• など,ucharの計算だけではおさまらない....

Page 35: 組み込み関数(intrinsic)によるSIMD入門

SIMD化しようとしてつまずくポイントSIMD化しようとしてつまずくポイント

画像処理のデ タは大体• 画像処理のデータは大体8bit入力,8bit出力だけれども,その中間は16bit(short),32bit(int)など,大きな型で計算( ), ( )な ,大 な 計算– 型の変換

• uchar a, b;• int c (int)a + (int)b• int c = (int)a + (int)b;

– こんな便利な型変換がない.saturate_cast<>とかはもちろんない

• さらに,カラー画像はRGBRGBRGB...とデータが並ぶため 個デ タを ドするとため16個データをロードすると– RGBRGBRGBRGBRGBR←一個余った...

• 処理が煩雑• 処理が煩雑

Page 36: 組み込み関数(intrinsic)によるSIMD入門

型変換型変換

さ ズ 大き ズ• 小さいサイズから大きいサイズへ

– uchar→ short→

– uchar→ int

• 大きいサイズから小さいサイズへ

– short→ uchar

int→uchar– int→uchar

Page 37: 組み込み関数(intrinsic)によるSIMD入門

uchar→ short (アライメント無視)uchar→ short (アライメント無視)

void cvtuchar2short(uchar* src, short* dest, int size){

for(int i=0;i<size;i+=8)for(int i=0;i<size;i+=8){

//8バイト刻みでロードする(下半分は無視)//8 イト刻みで ドする(下半分は無視)__m128i a = _mm_loadu_si128((__m128i*)(src+i));

//下位ビットをshortに変換する命令a  =_mm_cvtepu8_epi16(a);

//shortのデータは16ごとに書きこめるmm store si128(( m128i*)(dest+i) a);_mm_store_si128((__m128i*)(dest+i),a);

}}}

Page 38: 組み込み関数(intrinsic)によるSIMD入門

uchar→ short (アライメント考慮)uchar→ short (アライメント考慮)void cvtuchar2shortAligned(uchar* src, short* dest, int size){{

for(int i=0;i<size;i+=16){{

//16バイト刻みでロードする__m128i a = _mm_load_si128((__m128i*)(src+i));

//レジスタ全体を8バイト右シフトする__m128i b = _mm_srli_si128(a,8);

//下位ビ トを h tに変換する命令とストアを2回づつ//下位ビットをshortに変換する命令とストアを2回づつa  =_mm_cvtepu8_epi16(a);b = mm cvtepu8 epi16(b);b   _mm_cvtepu8_epi16(b);_mm_store_si128((__m128i*)(dest+i),a);_mm_store_si128((__m128i*)(dest+i+8),b);

}}

Page 39: 組み込み関数(intrinsic)によるSIMD入門

uchar→ short (アライメント考慮)uchar→ short (アライメント考慮)void cvtuchar2shortAligned2(uchar* src, short* dest, int size){{//全て0のレジスタを作る

const m128i zero = mm setzero si128();const __m128i zero = _mm_setzero_si128();for(int i=0;i<size;i+=16){

//16バイト刻みでロードする__m128i a = _mm_load_si128((__m128i*)(src+i));

//ゼロで入力のHigh Lowをアンパック128i b khi i8( )__m128i b = _mm_unpackhi_epi8(a,zero);

a = _mm_unpacklo_epi8(a,zero);//ストア//ストア

_mm_store_si128((__m128i*)(dest+i),a);_mm_store_si128((__m128i*)(dest+i+8),b);

}}

Page 40: 組み込み関数(intrinsic)によるSIMD入門

比較比較

• C++:  355.772 ms

• アライメント無視: 64 8431 msアライメント無視:  64.8431 ms

• アライメント考慮1: 44.4918 ms

• アライメント考慮2: 37.3332 ms

大9.5倍の高速化

Page 41: 組み込み関数(intrinsic)によるSIMD入門

uchar→ int (アライメント無視)uchar→ int (アライメント無視)

void cvtuchar2int(uchar* src, int* dest, int size){

for(int i=0;i<size;i+=4)for(int i=0;i<size;i+=4){

//4バイト刻みでロードする(12バイト分無視)//4 イト刻みで ドする( イト分無視)__m128i a = _mm_loadu_si128((__m128i*)(src+i));

//下位ビットをintに変換する命令a  =_mm_cvtepu8_epi32(a);_mm_store_si128((__m128i*)(dest+i),a);

}}}

Page 42: 組み込み関数(intrinsic)によるSIMD入門

uchar→ short (アライメント考慮)uchar→ short (アライメント考慮)void cvtuchar2intAligned(uchar* src, int* dest, int size){{

for(int i=0;i<size;i+=16){

//16バイト刻みでロードする// イ 刻 する__m128i a = _mm_load_si128((__m128i*)(src+i));

//intにしてストア__m128i b  =_mm_cvtepu8_epi32(a);_mm_store_si128((__m128i*)(dest+i),b);

//レジスタ全体を4バイト右シフトして変換してストアを4回繰り返すa = _mm_srli_si128(a,4);b  =_mm_cvtepu8_epi32(a);_mm_store_si128((__m128i*)(dest+i+4),b);a = _mm_srli_si128(a,4);b ( )b  =_mm_cvtepu8_epi32(a);_mm_store_si128((__m128i*)(dest+i+8),b);a = _mm_srli_si128(a,4);b t 8 i32( )b  =_mm_cvtepu8_epi32(a);_mm_store_si128((__m128i*)(dest+i+12),b);

}}

Page 43: 組み込み関数(intrinsic)によるSIMD入門

比較比較

• C++:  365.9 ms

• アライメント無視: 125 0 msアライメント無視: 125.0 ms

• アライメント考慮: 65.6 ms

大5.6倍の高速化

Page 44: 組み込み関数(intrinsic)によるSIMD入門

変換 mm cvt (SSE4 1)変換_mm_cvt (SSE4.1)

0 0_mm_cvtepu8_epi16 _mm_cvtepu8_epi32

下位ビットを任意の整数に変換する命令下位ビットを任意の整数に変換する命令__m128i _mm_cvtepi#_epi#(__m128i a)m128i mm cvtepu# epi#( m128i a)__m128i _mm_cvtepu#_epi#(__m128i a)

#8,16,32 #16,32,64

Page 45: 組み込み関数(intrinsic)によるSIMD入門

シフト mm slli si128 (SSE2)シフト_mm_slli_si128 (SSE2)

mm slli si128( m128i 4)0 0

mm srli si128( m128i 4)_mm_slli_si128(__m128i, 4) _mm_srli_si128(__m128i, 4)

レジスタ全体を右 左にシフトする関数レジスタ全体を右,左にシフトする関数右シフトで任意のビットを下位に持ってこれる

Page 46: 組み込み関数(intrinsic)によるSIMD入門

個別のシフト mm slli epi32 (SSE2)個別のシフト_mm_slli_epi32 (SSE2)

0 0

_mm_slli_epi32(__m128i, 2) _mm_srli_epi32 (__m128i, 2)

si128が全体シフトとなるのに対して,epi16,32,64など,個別に区切ってシフトも可能

Page 47: 組み込み関数(intrinsic)によるSIMD入門

整数シフト(SSE2)整数シフト(SSE2)

全体でシフト(論理)• Si128全体でシフト(論理)__m128i _mm_srli_si128(__m128i , int)__m128i _mm_slli_si128(__m128i , int)

• 個別にシフト(論理)– Slli,srli _mm_slli_epi32(__m128i, 2)など

• 全部同量のシフト場合

– Sll,Srl _mm_sll_epi32(__m128i, __m128i)• 要素ごとにシフト量が異なる場合

算• 算術右シフト– Sra,Srai

Page 48: 組み込み関数(intrinsic)によるSIMD入門

アンパック mm unpack (SSE2)アンパック _mm_unpack (SSE2)_mm_unpackhi_epi8(a,b);

数 位 バ を タ ブ2つの引数の上位8バイトをインタリーブ

_mm_unpacklo_epi8(a,b);_ _ p _ p ( )2つの引数の下位8バイトをインタリーブ

HIGH LHIGH Low

Page 49: 組み込み関数(intrinsic)によるSIMD入門

0とアンパック0とアンパック0  1  2  3  4  5  6  7   8 9 10 11 12 13 14 150 1 2 3 4 5 6 7 8 9 101112131415

0 0 1 0 2 0 3 0 4 0 5 0 6 0 7 08 0 9 0 10 0 11 0 12 0 13 0 14 0 15 0

ゼロとアンパックすると

HIGH Lowゼロとアンパックすると

shortのデータがうまく取り出せる

_mm_setzero_si128() レジスタを全て0にセットする関数

Page 50: 組み込み関数(intrinsic)によるSIMD入門

大きなものから小さなものへ大きなものから小さなものへ

• Short uchar

• Int ucharInt uchar

Page 51: 組み込み関数(intrinsic)によるSIMD入門

shortからucharshort からuchar

void cvtshort2ucharAligned (short* src, uchar* dest, int size){

for(int i=0;i<size;i+=16)for(int i=0;i<size;i+=16){

//shortのデータ16個を2つのレジスタにロード//shortのデ タ 6個を のレジスタに ド__m128i a = _mm_load_si128((__m128i*)(src+i));__m128i b = _mm_load_si128((__m128i*)(src+i+8));

ジ タをパ デ タ 変換//二つのレジスタをパックしてcharのデータに変換してストアa = _mm_packs_epi16(a,b);mm store si128(( m128i*)(dest+i) a);_mm_store_si128((__m128i*)(dest+i),a);

}}}

Page 52: 組み込み関数(intrinsic)によるSIMD入門

intからucharintからuchar

void cvtint2ucharAligned(int* src, uchar* dest, int size){

for(int i=0;i<size;i+=16){

//intのデータ16個を4つのレジスタにロードm128i a = mm load si128(( m128i*)(src+i));__m128i a   _mm_load_si128((__m128i )(src i));

__m128i b = _mm_load_si128((__m128i*)(src+i+4));__m128i c = _mm_load_si128((__m128i*)(src+i+8));m128i d = mm load si128(( m128i*)(src+i+12));__m128i d = _mm_load_si128((__m128i )(src+i+12));

//上位下位二つのレジスタをパックして2つのshortのデータに変換a = _mm_packs_epi32(a,b);

k i32( d)c = _mm_packs_epi32(c,d);//shortをパックしてストア

a = _mm_packs_epi16(a,c);_mm_store_si128((__m128i*)(dest+i),a);

}}

Page 53: 組み込み関数(intrinsic)によるSIMD入門

実験結果実験結果

Short to uchar

• C++: 341.8 mC++: 341.8 m

• SSE: 35.4 ms 9 7倍の高速化Int to ucahr

9.7倍の高速化Int to ucahr

• C++: 372.1 ms

• SSE: 45.3 ms8.2倍の高速化8.2倍の高速化

Page 54: 組み込み関数(intrinsic)によるSIMD入門

パック mm packsパック _mm_packs

_mm_packs_epi16

2つのレジスタの各値が飽和演算されながらつぶされ,上位ビ ト 位ビ ト 整列される上位ビット下位ビットへ整列される

Page 55: 組み込み関数(intrinsic)によるSIMD入門

パックx2int x int→short パックx2int x int→short_mm_packs_epi32

short x short →uchar_mm_packs_epi16

Page 56: 組み込み関数(intrinsic)によるSIMD入門

データの並び替えデータの並び替え

な き 必• こんなときに必要

– 逆順ソート(フリップ)逆順ソ ( リッ )

– スワップ

RGBRGBRGB RRRGGGBBB– RGBRGBRGB RRRGGGBBB

などの並び替え

Page 57: 組み込み関数(intrinsic)によるSIMD入門

逆順ソート逆順ソートvoid flipdata(uchar* src, uchar* dest, const int size){{//逆順を設定するシャッフル用のマスクを設定

const __m128i mask = _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0);

//ソースのポインタを 後から16バイト前の位置に移動uchar* s = src+size‐16;for(int i=0;i<size;i+=16){

m128i a = mm load si128(( m128i*)(s));__m128i a   _mm_load_si128((__m128i )(s));//逆順にシャッフル

a = _mm_shuffle_epi8(a,mask);mm store si128(( m128i*)(dest+i) a);_mm_store_si128((__m128i )(dest+i),a);

//ソースのポインタを16バイトデクリメントs‐=16;

}}}

Page 58: 組み込み関数(intrinsic)によるSIMD入門

比較比較

• C++ 50ms

• SSE 5.7msSSE 5.7ms

8 8倍高速化8.8倍高速化

Page 59: 組み込み関数(intrinsic)によるSIMD入門

シャッフル mm shuffle(SSE2)シャッフル _mm_shuffle(SSE2)mm shuffle epi8(a,mask);_mm_shuffle_epi8(a,mask);Maskには入力の行き先を設定mm setr epi8:レジスタを個別にセットする関数_mm_setr_epi8:レジスタを個別にセットする関数

__m128i mask=_mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0);

__m128i mask=_mm_setr_epi8(2,1,0,5,4,3,8,7,6,11,10,9,14,13,12,15);

Page 60: 組み込み関数(intrinsic)によるSIMD入門

vs OpenCV編

Page 61: 組み込み関数(intrinsic)によるSIMD入門

MenuMenu

ピピコピー a.copyTo(b)変換 a.convertTo(b,CV 16S)コピー a.copyTo(b)変換 a.convertTo(b,CV 16S)変換 a.convertTo(b,CV_16S)加算 c = a + b, cv::add(a,b,c)乗算 l(b) lti l ( b )

変換 a.convertTo(b,CV_16S)加算 c = a + b, cv::add(a,b,c)乗算 l(b) lti l ( b )乗算 c = a.mul(b) , cv::multiply(a,b,c)変換2 a.convertTo(b,CV_16S,5,10)乗算 c = a.mul(b) , cv::multiply(a,b,c)変換2 a.convertTo(b,CV_16S,5,10)( , _ , , )反転 cv::flip(a,b,‐1)色変換1 cvtColor(a b CV BGR2RGB)

( , _ , , )反転 cv::flip(a,b,‐1)色変換1 cvtColor(a b CV BGR2RGB)色変換1 cvtColor(a,b,CV_BGR2RGB)分離 cv::slipt(a,v)色変換1 cvtColor(a,b,CV_BGR2RGB)分離 cv::slipt(a,v)色変換2 cvtColor(a,b,CV_BGR2Gray)色変換2 cvtColor(a,b,CV_BGR2Gray)

Page 62: 組み込み関数(intrinsic)によるSIMD入門

実験条件実験条件

ま ポ• Intel Core i7 920 SSE4.2までサポート

• 2 66→2 93GHzまでオーバークロックして使用2.66→2.93GHzまでオ バ クロックして使用

• OpenCVの関数

• も簡単?と思われる実装を自前で(C++)も簡単?と思われる実装を自前で(C++)

• SSEによる実装

の3つを比較

Page 63: 組み込み関数(intrinsic)によるSIMD入門

先に結果先に結果

ピ 効果なしピ 効果なしコピー 効果なし変換 1.5倍コピー 効果なし変換 1.5倍変換 1.5倍加算 1.4倍乗算 4 0倍

変換 1.5倍加算 1.4倍乗算 4 0倍乗算 4.0倍変換2 9.8倍乗算 4.0倍変換2 9.8倍反転 7.0倍色変換1 5 7倍反転 7.0倍色変換1 5 7倍色変換1 5.7倍分離 3.8倍色変換1 5.7倍分離 3.8倍色変換2 4.5倍色変換2 4.5倍

Page 64: 組み込み関数(intrinsic)によるSIMD入門

コピー a copyTo(b)コピー a.copyTo(b)void copy8SSE(const Mat& src, Mat& dest)void copy8SSE(const Mat& src, Mat& dest){

const uchar* s = src.data;uchar* d = dest data;uchar  d = dest.data;const int size = src.size().area()*src.channels();for(int i=0;i<size;i+=16){{

__m128i a = _mm_load_si128((__m128i*)(s+i));_mm_store_si128((__m128i*)(d+i),a);

}}

16バイト単位でロードとストアを繰り返すだけ!繰り返すだけ!

Page 65: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 68.2 ms• C++ 308.4ms• SSE 62.8 ms

コメント– さすがにコピーは変わらない

– SSEを使ってでコピーしているところをmemcpyでpy書いても同じ速度

– 自前でループ展開して代入するよりは速い.

Page 66: 組み込み関数(intrinsic)によるSIMD入門

変換 a convertTo(b CV 16U)変換 a.convertTo(b,CV_16U)void cvtuchar2ushortMatSSE(Mat& a,Mat& b){{

const int size = a.size().area()*a.channels();const uchar* s = a.ptr<uchar>(0);

i d h * d b i d h (0)unsigned short* d = b.ptr<unsigned short>(0);const __m128i zero = _mm_setzero_si128();for(int i=0;i<size;i+=16){

__m128i a = _mm_load_si128((__m128i*)(s+i));__m128i b = _mm_unpackhi_epi8(a,zero);__ _ _ p _ p ( , );a = _mm_unpacklo_epi8(a,zero);_mm_store_si128((__m128i*)(d+i),a);mm store si128(( m128i*)(d+i+8),b);_mm_store_si128((__m128i )(d+i+8),b);

}} 必要な型変換を実装するだけ必要な型変換を実装するだけ

(本当はax+bが出来る関数だけど今回は無視)

Page 67: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 159.2 ms

• C++ 206.5 msC++ 206.5 ms

• SSE 100.7 ms

コメントコメント

– 型変換はOpenCVに実装されてないため高速化されるれる.

Page 68: 組み込み関数(intrinsic)によるSIMD入門

加算 c = a + b cv::add(a b c)加算 c = a + b, cv::add(a,b,c)void add8SSE(Mat& src1, Mat& src2, Mat& dest){

uchar* pa = src1.data;uchar* pb = src2.data;uchar* pc = dest datadest.data;

const int size = src.size().area()*src.channels();for(int i=0;i<size;i+=16)for(int i 0;i size;i 16){

__m128i a = _mm_load_si128((__m128i*)(pa+i));__m128i b = _mm_load_si128((__m128i*)(pb+i));

//飽和演算つきunsignedの加算a mm adds epu8(a b);a = _mm_adds_epu8(a,b);_mm_store_si128((__m128i*)(pc+i),a);

}}}

Page 69: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 101.1 ms

• C++ 524.9 msC++ 524.9 ms

• SSE 71.4 ms

コメントコメント

– 例外処理が無いことやアライメントをそろえている分O CVの関数より高速分OpenCVの関数より高速

Page 70: 組み込み関数(intrinsic)によるSIMD入門

乗算(1/2) c = a mul(b) cv::multiply(a b c)乗算(1/2)    c = a.mul(b) ,cv::multiply(a,b,c)void multiply8SSE(const Mat& src1, const Mat& src2,Mat& dest){//ロードしたりする前処理

uchar* s1 src1 data; uchar* s2 src2 data;uchar* duchar* s1 = src1.data; uchar* s2 = src2.data;uchar* d = dest.data;

const int size = src1.size().area()*src1.channels();const int si e src .si e().area() src .channels();const __m128i zero = _mm_setzero_si128();for(int i=0;i<size;i+=16){

//ロードデータを2本ストアm128i a = mm load si128(( m128i*)(s1+i));__m128i a = _mm_load_si128((__m128i*)(s1+i));

__m128i b = _mm_load_si128((__m128i*)(s2+i));

Page 71: 組み込み関数(intrinsic)によるSIMD入門

乗算(2/2) c = a mul(b) cv::multiply(a b c)乗算(2/2)    c = a.mul(b) ,cv::multiply(a,b,c)//入力をhigh,lowにアンパック

__m128i a1 = _mm_unpackhi_epi8(a,zero);__m128i a2 = _mm_unpacklo_epi8(a,zero);m128i b1 mm unpackhi epi8(b zero);__m128i b1 = _mm_unpackhi_epi8(b,zero);

__m128i b2 = _mm_unpacklo_epi8(b,zero);//High,Lowごとに乗算(8bit乗算命令はない)して下位ビットをとりだ//High,Lowごとに乗算(8bit乗算命令はない)して下位ビットをとりだす

a = _mm_mullo_epi16(a1,b1);b = _mm_mullo_epi16(a2,b2);

//2つの計算結果をアンパックしてストアa = mm packus epi16(a b);a = _mm_packus_epi16(a,b);_mm_store_si128((__m128i*)(d+i),a);

}}}

Page 72: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 405.8 ms

• C++ 409.3 msC++ 409.3 ms

• SSE 106.1 ms

コメントコメント

– OpenCVには実装されていない命令のためかなり高速化に成功高速化に成功

Page 73: 組み込み関数(intrinsic)によるSIMD入門

変換2(1/2) a convertTo(b CV 16U 5 10)変換2(1/2) a.convertTo(b,CV_16U,5,10)void convertTouchar2ushortMatSSE(Mat& src,Mat& dest, const intl h i b )alpha, const int beta){

const int size = src size() area()*src channels();const int size = src.size().area() src.channels();const uchar* s = src.data;unsigned short* d = dest.ptr<unsigned short>(0);

//0をセットするのと16ビットで8つの定数alpha.betaをセットconst __m128i zero = _mm_setzero_si128();

t 128i A t1 i16( l h )const __m128i A = _mm_set1_epi16(alpha);const __m128i B = _mm_set1_epi16(beta);for(int i=0;i<size;i+=16)for(int i 0;i<size;i+ 16){

__m128i a = _mm_load_si128((__m128i*)(s+i));

Page 74: 組み込み関数(intrinsic)によるSIMD入門

変換2(2/2) a convertTo(b CV 16U 5 10)変換2(2/2) a.convertTo(b,CV_16U,5,10)//ロードしたレジスタを上位でアンパックして乗算

128i b khi i8( )__m128i b = _mm_unpackhi_epi8(a,zero);b = _mm_mullo_epi16(b,A);

//ロードしたレジスタを下位でアンパックして乗算//ロ ドしたレジスタを下位でアンパックして乗算a = _mm_unpacklo_epi8(a,zero);a = _mm_mullo_epi16(a,A);

//計算結果を加算(shotrでの計算結果を得る)a = _mm_adds_epi16(a,B);b dd i16(b B)b = _mm_adds_epi16(b,B);

//shortは8ごとでも16境界なので2つストアmm store si128(( m128i*)(d+i) a);_mm_store_si128((__m128i )(d+i),a);_mm_store_si128((__m128i*)(d+i+8),b);

}}

Page 75: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 1067.5 ms

• C++ 307.6 msC++ 307.6 ms

• SSE 109.9 ms

• コメントコメント

– convertToとかかなり使うのに,OpenCVはどんな実装してるんだか実装してるんだか...

– 乗算と加算,型変換のSSE化なのでかなり速い

Page 76: 組み込み関数(intrinsic)によるSIMD入門

反転 cv::flip(a b 1)反転 cv::flip(a,b,‐1)void flipSSE__(Mat& src, Mat& dest) //上下左右反転のみ{//反転用のマスク生成

const m128i mask =const __m128i mask = _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0);const int size  =src.size().area()*src.channels();const int si e src.si e().area() src.channels();uchar* s = src.data+size‐16;uchar* d = dest.data;逆 ド 転//入力を逆からロードして反転してストア

for(int i=0;i<size;i+=16){{

__m128i a = _mm_load_si128((__m128i*)(s));a =  mm shuffle epi8(a,mask);_ _ _ p ( , );_mm_store_si128((__m128i*)(d+i),a);s‐=16;

Page 77: 組み込み関数(intrinsic)によるSIMD入門

反転 cv::flip(a b code)全対応反転 cv::flip(a,b,code)全対応void flip8UC1SSE(Mat& src, Mat& dest, int flipCode){

if(flipCode==0){

t i t i l /16const int size = src.cols/16;uchar* ss = src.data;uchar* dd = dest.data+(src.rows‐1)*src.cols;for(int j=src.rows;j‐‐;){

__m128i* s = (__m128i*)(ss);__m128i* d = (__m128i*)(dd);for(int i=size;i‐‐;){

m128i a = mm load si128(s++);__m128i a   _mm_load_si128(s );_mm_store_si128((d++),a);

}ss+=src.cols;dd‐=src.cols;

}}else if(flipCode==1){

const int size = src.cols/16;const __m128i mask = _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0);uchar* ss = src.data + src.cols ‐16;uchar* dd = dest.data;for(int j=src.rows;j‐‐;){

__m128i* s = (__m128i*)(ss);__m128i* d = (__m128i*)(dd);for(int i=size;i‐‐;){

m128i a = mm load si128(s );__m128i a = _mm_load_si128(s‐‐);a = _mm_shuffle_epi8(a,mask);_mm_store_si128((d++),a);

}ss+=src.cols;dd+=src.cols;

}}else if(flipCode==‐1){{

const __m128i mask = _mm_setr_epi8(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0);const int size  =src.size().area()*src.channels();uchar* s = src.data+size‐16;uchar* d = dest.data;for(int i=0;i<size;i+=16){

__m128i a = _mm_load_si128((__m128i*)(s));a = _mm_shuffle_epi8(a,mask);_mm_store_si128((__m128i*)(d+i),a);

6s‐=16;}

}}

Page 78: 組み込み関数(intrinsic)によるSIMD入門

結果結果

フリップには3種類(左右反転 上下反転 上下左右反転)• フリップには3種類(左右反転,上下反転,上下左右反転)• 左右 :OpenCV 375.6 ms C++ 313.4 ms SSE 68.8 ms• 上下 :OpenCV 88 30 ms C++ 59 59 ms SSE 64 0 ms上下 :OpenCV 88.30 ms C++ 59.59 ms SSE 64.0 ms• 上下左右 :OpenCV 467.3 ms C++ 306.5 ms SSE 65.8 msコメント

– 上下反転はSSEでもほとんど変わらない(逆順にmemcpyするだけ)

– 上下左右反転 左右反転の結果はかなりいい上下左右反転,左右反転の結果はかなりいい– Flipはビデオ入力とかでも良く使うので有望?– ただしRGBのカラー処理ではないので,ちゃんとRGBように作ら

ないと使えないかなないと使えないかな

Page 79: 組み込み関数(intrinsic)によるSIMD入門

色変換色変換

問題 画素を16個ロ ドすると先頭は問題:画素を16個ロードすると先頭は...RGBRGBRGBRGBRGB R 一個余る

戦略戦略1. 後のあまりを無視する2 3個集めて 8個づつ処理すればあまりなし!2. 3個集めて48個づつ処理すればあまりなし!

RGBRGBRGBRGBRGBR

GBRGBRGBRGBRGBRGswap

swap

BRGBRGBRGBRGBRGBswap

Page 80: 組み込み関数(intrinsic)によるSIMD入門

色変換 cv::cvtColor(a b CV BGR2RGB)色変換 cv::cvtColor(a,b,CV_BGR2RGB)void cvtBGR2RGB_SSE_nonal(Mat& src,Mat& dest){

i i i () ()* h l ()const int size = src.size().area()*src.channels();const uchar* s = src.data; uchar* d = dest.data;

m128i mask =__m128i mask = _mm_setr_epi8(2,1,0,5,4,3,8,7,6,11,10,9,14,13,12,

15);//ここを捨てる.//BGRBGRBGRBGRBGR Bfor(int i=0;i<size;i+=15)//画素5x3づつ処理する{

//境界をそろ てなくても使えるロ ド//境界をそろってなくても使えるロード__m128i a = _mm_loadu_si128((__m128i*)(s+i));

//シャッフル//シャッフルa = _mm_shuffle_epi8(a,mask);

//境界をそろってなくても使えるストア_mm_storeu_si128((__m128i*)(d+i),a);

}}

Page 81: 組み込み関数(intrinsic)によるSIMD入門

色変換高速版(1/3) cv::cvtColor(a b CV BGR2RGB)色変換高速版(1/3) cv::cvtColor(a,b,CV_BGR2RGB)

void cvtBGR2RGB_SSE(Mat& src,Mat& dest){{//前準備

const int size = src size() area()*src channels();const int size = src.size().area() src.channels();uchar* s = src.data;uchar* d = dest.data;__m128i mask1 = //BGR BGR BGR BGR BGR B

_mm_setr_epi8(2,1,0,5,4,3,8,7,6,11,10,9,14,13,12,15);__m128i mask2 = //GR BGR BGR BGR BGR BGt i8(0 14 4 3 2 7 6 5 10 9 8 13 12 14 1 15)_mm_setr_epi8(0,14,4,3,2,7,6,5,10,9,8,13,12,14,1,15);__m128i mask3 = //R BGR BGR BGR BGR BGR

mm setr epi8(0 3 2 1 6 5 4 9 8 7 12 11 10 15 14 13);_mm_setr_epi8(0,3,2,1,6,5,4,9,8,7,12,11,10,15,14,13);//48画素一気に処理

for(int i=0;i<size;i+=48){

Page 82: 組み込み関数(intrinsic)によるSIMD入門

色変換高速版(1/3) cv::cvtColor(a b CV BGR2RGB)色変換高速版(1/3) cv::cvtColor(a,b,CV_BGR2RGB)

//16画素x3個3つのレジスタに分けてロード128i l d i128(( 128i*)( i))__m128i a = _mm_load_si128((__m128i*)(s+i));

__m128i b = _mm_load_si128((__m128i*)(s+i+16));m128i c = mm load si128(( m128i*)(s+i+32));__m128i c = _mm_load_si128((__m128i )(s+i+32));

//レジスタをまたぐ値を抽出int Ba = _mm_extract_epi8(a,15);int Rb = _mm_extract_epi8(b,1);int Bb = _mm_extract_epi8(b,14);i t R t t i8( 0)int Rc = _mm_extract_epi8(c,0);

//シャッフルしてRGBRGBRGBRGBRっぽい値にスワップa = mm shuffle epi8(a mask1);a   _mm_shuffle_epi8(a,mask1);b = _mm_shuffle_epi8(b,mask2);c = _mm_shuffle_epi8(c,mask3);

Page 83: 組み込み関数(intrinsic)によるSIMD入門

色変換高速版(1/3) cv::cvtColor(a b CV BGR2RGB)色変換高速版(1/3) cv::cvtColor(a,b,CV_BGR2RGB)

//レジスタ間でまたぐ値を挿入してi i8( Rb 15)a=_mm_insert_epi8(a,Rb,15);

b=_mm_insert_epi8(b,Ba,1);b= mm insert epi8(b Rc 14);b=_mm_insert_epi8(b,Rc,14);c=_mm_insert_epi8(c,Bb,0);

//16画素x3個ストア_mm_store_si128((__m128i*)(d+i),a);_mm_store_si128((__m128i*)(d+i+16),b);

t i128(( 128i*)(d i 32) )_mm_store_si128((__m128i*)(d+i+32),c);}

}}

Page 84: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 392.3 ms

• C++ 265.2 msC++ 265.2 ms

• SSE 99.3 ms

• SSE 68.8 ms (アライメントまで考慮)

コメントコメント

– 良く使う割にOpenCVの関数は遅い

Page 85: 組み込み関数(intrinsic)によるSIMD入門

分離 cv::split(a v)分離 cv::split(a,v)

素を 個 ドする 先問題:画素を16個ロードすると先頭は...

RGBRGBRGBRGBRGB R 一個余るRGBRGBRGBRGBRGB R 個余る

戦略

づ– 3個集めて48個づつ処理

RGBRGBRGBRGBRGBR RRRRRRRRRRRRRRRR

GBRGBRGBRGBRGBRG GGGGGGGGGGGGGGG

BRGBRGBRGBRGBRGB BBBBBBBBBBBBBBBB

16個づつに並びかえて書きこみ

Page 86: 組み込み関数(intrinsic)によるSIMD入門

分離(1/8) cv::split(a v)分離(1/8) cv::split(a,v)void splitSSE(Mat& src, vector<Mat>& dest){//サイズやポインタをセット

const int size = src.size().area()*src.channels();uchar* s = src.ptr<uchar>(0);uchar* B = dest[0] ptr<uchar>(0);uchar  B = dest[0].ptr<uchar>(0);uchar* G = dest[1].ptr<uchar>(0);uchar* R = dest[2].ptr<uchar>(0);

//48個(16x3)ロードするとこんな並び順//BGR BGR BGR BGR BGR B//GR BGR BGR BGR BGR BG//R BGR BGR BGR BGR BGR// Bを作る1本目の1回目シャッフル:BBBBBBGGGGGRRRRRconst __m128i mask1 = _mm_setr_epi8(0,3,6,9,12,15,1,4,7,10,13,2,5,8,11,14);// Gを作る1本目の2回目シャッフル:GGGGGBBBBBBRRRRRconst __m128i smask1 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);// Rを作る1本目の3回目シャッフル:RRRRRGGGGGBBBBBB//const __m128i ssmask1 = _mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);

// Bを作る2本目の1回目シャッフル: GGGGGGBBBBBRRRRRconst __m128i mask2 = _mm_setr_epi8(0,3,6,9,12,15, 2,5,8,11,14,1,4,7,10,13);// Gを作る2本目の2回目シャッフル:BBBBBGGGGGGRRRRR//const __m128i smask2 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);//Rを作る2本目の3回目シャッフル:BBBBBRRRRRGGGGGGconst __m128i ssmask2 = _mm_setr_epi8(0,1,2,3,4,11,12,13,14,15,5,6,7,8,9,10);

// Bを作る3本目の1回目シャッフル: RRRRRRGGGGGBBBBB//__m128i mask3 = _mm_setr_epi8(0,3,6,9,12,15, 2,5,8,11,14,1,4,7,10,13);//同じマスクがあるので代用

があ//const __m128i smask3 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,6,7,8,9,10); //同じマスクがあるので代用//const __m128i ssmask3 = _mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10); //同じマスクがあるので代用

//ブレンドマスクconst __m128i bmask1 = _mm_setr_epi8

(255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0);const __m128i bmask2 = _mm_setr_epi8(255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0);const __m128i bmask3 = _mm_setr_epi8

(255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0);const __m128i bmask4 = _mm_setr_epi8(255 255 255 255 255 255 255 255 255 255 0 0 0 0 0 0)(255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0);

__m128i a,b,c;for(int i=0;i<size;i+=48){

a = _mm_stream_load_si128((__m128i*)(s+i));b = _mm_stream_load_si128((__m128i*)(s+i+16));c = _mm_stream_load_si128((__m128i*)(s+i+32));a = _mm_load_si128((__m128i*)(s+i));b = _mm_load_si128((__m128i*)(s+i+16));c = _mm_load_si128((__m128i*)(s+i+32));

__m128i v = _mm_blendv_epi8(b,a,bmask1);v = _mm_blendv_epi8(c,v,bmask2);_mm_stream_si128((__m128i*)(B),v);

a = _mm_shuffle_epi8(a,smask1);b = _mm_shuffle_epi8(b,smask1);c = _mm_shuffle_epi8(c,smask1);v = _mm_blendv_epi8(b,a,bmask3);v = _mm_blendv_epi8(c,v,bmask2);_mm_stream_si128((__m128i*)(G),v);

a = _mm_shuffle_epi8(a,ssmask1);c = _mm_shuffle_epi8(c,ssmask1);b = _mm_shuffle_epi8(b,ssmask2);

v = _mm_blendv_epi8(b,a,bmask3);v = _mm_blendv_epi8(c,v,bmask4);_mm_stream_si128((__m128i*)(R),v);

B+=16;G+=16;R+=16;}

}

Page 87: 組み込み関数(intrinsic)によるSIMD入門

分離(2/8) cv::split(a v)分離(2/8) cv::split(a,v)void splitSSE(Mat& src, vector<Mat>& dest){//サイズやポインタをセット

const int size = src size() area()*src channels();const int size = src.size().area() src.channels();uchar* s = src.ptr<uchar>(0);uchar* B = dest[0].ptr<uchar>(0);[ ] p ( );uchar* G = dest[1].ptr<uchar>(0);uchar* R = dest[2].ptr<uchar>(0);

// 個( ) ドすると んな並び順//48個(16x3)ロードするとこんな並び順//BGR BGR BGR BGR BGR B//GR BGR BGR BGR BGR BG//GR BGR BGR BGR BGR BG//R BGR BGR BGR BGR BGR

Page 88: 組み込み関数(intrinsic)によるSIMD入門

分離(3/8) cv::split(a v)分離(3/8) cv::split(a,v)// Bを作る1本目の1回目シャッフル:BBBBBBGGGGGRRRRRconst __m128i mask1 = 

_mm_setr_epi8(0,3,6,9,12,15,1,4,7,10,13,2,5,8,11,14);// Gを作る1本目の2回目シャッフル:GGGGGBBBBBBRRRRR// Gを作る1本目の2回目シャッフル:GGGGGBBBBBBRRRRRconst __m128i smask1 = 

mm setr epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);_ _ _ p ( , , , , , , , , , , , , , , , );// Rを作る1本目の3回目シャッフル:RRRRRGGGGGBBBBBBconst __m128i ssmask1 = 

( )_mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);

Page 89: 組み込み関数(intrinsic)によるSIMD入門

分離(4/8) cv::split(a v)分離(4/8) cv::split(a,v)// Bを作る2本目の1回目シャッフル: GGGGGGBBBBBRRRRRconst __m128i mask2 = _mm_setr_epi8(0,3,6,9,12,15, 

2,5,8,11,14,1,4,7,10,13);// Gを作る2本目の2回目シャッフル:BBBBBGGGGGGRRRRR// Gを作る2本目の2回目シャッフル:BBBBBGGGGGGRRRRR//const __m128i smask2 = 

mm setr epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);_ _ _ p ( , , , , , , , , , , , , , , , );//Rを作る2本目の3回目シャッフル:BBBBBRRRRRGGGGGGconst __m128i ssmask2 = 

( )_mm_setr_epi8(0,1,2,3,4,11,12,13,14,15,5,6,7,8,9,10);

Page 90: 組み込み関数(intrinsic)によるSIMD入門

分離(5/8) cv::split(a v)分離(5/8) cv::split(a,v)// Bを作る3本目の1回目シャッフル: RRRRRRGGGGGBBBBB//__m128i mask3 = _mm_setr_epi8(0,3,6,9,12,15, 

2,5,8,11,14,1,4,7,10,13);//同じマスクがあるので代用//const m128i smask3 =//const __m128i smask3 = 

_mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,6,7,8,9,10); //同じマスクがあるので代用代用

//const __m128i ssmask3 = _mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10); //同じマスクがあるの 代用があるので代用

Page 91: 組み込み関数(intrinsic)によるSIMD入門

分離(6/8) cv::split(a v)分離(6/8) cv::split(a,v)//ブレンドマスク

const __m128i bmask1 = _mm_setr_epi8(255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0);

const m128i bmask2 = mm setr epi8const __m128i bmask2 = _mm_setr_epi8(255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0);const  m128i bmask3 =  mm setr epi8__ _ _ _ p

(255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0);const __m128i bmask4 = _mm_setr_epi8( )(255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0);

//48(16x3)画素ごと処理開始//48(16x3)画素ごと処理開始__m128i a,b,c;

for(int i=0;i<size;i+=48)( ; ; ){

Page 92: 組み込み関数(intrinsic)によるSIMD入門

分離(7/8) cv::split(a v)分離(7/8) cv::split(a,v)//16個づつa,b,cにロード

a = _mm_load_si128((__m128i*)(s+i));b = _mm_load_si128((__m128i*)(s+i+16));c = mm load si128(( m128i*)(s+i+32));c = _mm_load_si128((__m128i )(s+i+32));

//ブレンドを使ってBBBBBBBBBBBBBBBBに変換m128i v =  mm blendv epi8(b,a,bmask1);__ _ _ _ p ( , , );

v = _mm_blendv_epi8(c,v,bmask2);_mm_stream_si128((__m128i*)(B),v);

//シ とブ ドを使 に変換//シャッフルとブレンドを使ってGGGGGGGGGGGGGGGGに変換a = _mm_shuffle_epi8(a,smask1);b = mm shuffle epi8(b smask1);b = _mm_shuffle_epi8(b,smask1);c = _mm_shuffle_epi8(c,smask1);v = _mm_blendv_epi8(b,a,bmask3);_ _ _ p ( , , );v = _mm_blendv_epi8(c,v,bmask2);_mm_stream_si128((__m128i*)(G),v);

Page 93: 組み込み関数(intrinsic)によるSIMD入門

分離(8/8) cv::split(a v)分離(8/8) cv::split(a,v)//シャッフルとブレンドを使ってRRRRRRRRRRRRRRRRに変換

a = _mm_shuffle_epi8(a,ssmask1);c = _mm_shuffle_epi8(c,ssmask1);b = mm shuffle epi8(b ssmask2);b = _mm_shuffle_epi8(b,ssmask2);v = _mm_blendv_epi8(b,a,bmask3);v =  mm blendv epi8(c,v,bmask4);_ _ _ p ( , , );_mm_stream_si128((__m128i*)(R),v);

//16画素分進めるB+=16;G+=16;R+=16;

}}}

Page 94: 組み込み関数(intrinsic)によるSIMD入門

結果結果

• OpenCV 697.1 ms

• C++ 468.7 msC++ 468.7 ms

• SSE 171.4 ms

コメント

– RGBを並べ替えるのは結構めんどくさい...RGBを並 替えるのは結構めんどくさい...

– これベースにカラー画像のSSE化が可能

RRRRGGGGBBBBに並べ替えるのは 「速度的にこ– RRRRGGGGBBBBに並べ替えるのは,「速度的にこれってベストなの?」という疑問は残るが...

Page 95: 組み込み関数(intrinsic)によるSIMD入門

色変換 cv::cvtColor(a b CV BGR2Gray)色変換 cv::cvtColor(a,b,CV_BGR2Gray)

戦略戦略• 固定小数点をshortの精度で行う

(精度は悪いけど 気にしない)– (精度は悪いけど,気にしない)

• 48個ロードして,BGRをB16個G16個C16個に並び変え変え

• Shortにアンパックして,R,G,Bの固定小数点演算算

• R,G,Bを加算加算の結果を8ビットシフト• 加算の結果を8ビットシフト

• Shortをパックしてストア

Page 96: 組み込み関数(intrinsic)によるSIMD入門

色変換(1/11)cv::cvtColor(a,b,CV_BGR2Gray)

void cvtBGR2GraySSEShort(Mat& src, Mat& dest){

const int size = src.size().area()*src.channels();uchar* s = src.ptr<uchar>(0);uchar  s   src.ptr uchar (0);uchar* d = dest.ptr<uchar>(0);//スプリットをしてから,RGBからグレイに変換//BBBBBBGGGGGRRRRRへシャッフル(分離)const __m128i mask1 = _mm_setr_epi8(0,3,6,9,12,15,1,4,7,10,13,2,5,8,11,14);const __m128i smask1 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);const __m128i ssmask1 = _mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);

//GGGGGGBBBBBRRRRRへシャッフルconst __m128i mask2 = _mm_setr_epi8(0,3,6,9,12,15, 2,5,8,11,14,1,4,7,10,13);//const __m128i smask2 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);smask1と同じconst __m128i ssmask2 = _mm_setr_epi8(0,1,2,3,4,11,12,13,14,15,5,6,7,8,9,10);

//RRRRRRGGGGGBBBBBへシャッフル// m128i mask3 =  mm setr epi8(0,3,6,9,12,15, 2,5,8,11,14,1,4,7,10,13);//mask2と同じ//__ _ _ _ p ( , , , , , , , , , , , , , , , );// 同//const __m128i smask3 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,6,7,8,9,10);//smask1と同じ//const __m128i ssmask3 = _mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);//ssmask1と同じ

//ブレンドマスクconst __m128i bmask1 = _mm_setr_epi8

(255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0);

const __m128i bmask2 = _mm_setr_epi8(255,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0);

const __m128i bmask3 = _mm_setr_epi8(255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0);

const __m128i bmask4 = _mm_setr_epi8__ _ _ _ p(255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0);

const int shift = 8;const int amp = 1<<shift;const int _R_=(int)(amp*0.299);const int _G_=(int)(amp*0.587);const int _B_=(int)(amp*0.114);const __m128i R = _mm_set1_epi16(_R_);const __m128i G = _mm_set1_epi16(_G_);const __m128i B = _mm_set1_epi16(_B_);const __m128i zero = _mm_setzero_si128();

for(int i=0;i<size;i+=48){

__m128i a = _mm_shuffle_epi8(_mm_load_si128((__m128i*)(s+i)),mask1);__m128i b = _mm_shuffle_epi8(_mm_load_si128((__m128i*)(s+i+16)),mask2);__m128i c = _mm_shuffle_epi8(_mm_load_si128((__m128i*)(s+i+32)),mask2);const __m128i aaaa = _mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask1),bmask2);

a = _mm_shuffle_epi8(a,smask1);b = _mm_shuffle_epi8(b,smask1);c = _mm_shuffle_epi8(c,smask1);const __m128i bbbb =_mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask3),bmask2);

a = _mm_shuffle_epi8(a,ssmask1);c = _mm_shuffle_epi8(c,ssmask1);b = _mm_shuffle_epi8(b,ssmask2);const __m128i cccc =_mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask3),bmask4);

__m128i a1 = _mm_unpackhi_epi8(aaaa,zero);__m128i a2 = _mm_unpacklo_epi8(aaaa,zero);a1 = _mm_mullo_epi16(a1,B);a2 = _mm_mullo_epi16(a2,B);

__m128i b1 = _mm_unpackhi_epi8(bbbb,zero);__m128i b2 = _mm_unpacklo_epi8(bbbb,zero);b1 = _mm_mullo_epi16(b1,G);b2 = _mm_mullo_epi16(b2,G);

__m128i c1 = _mm_unpackhi_epi8(cccc,zero);__m128i c2 = _mm_unpacklo_epi8(cccc,zero);c1 = _mm_mullo_epi16(c1,R);c2 = _mm_mullo_epi16(c2,R);

a1 = _mm_add_epi16(a1,b1);a1 = _mm_add_epi16(a1,c1);a2 = _mm_add_epi16(a2,b2);a2 = _mm_add_epi16(a2,c2);

a1 = _mm_srli_epi16(a1,8);a2 = _mm_srli_epi16(a2,8);

a = _mm_packus_epi16(a1,a2);

_mm_stream_si128((__m128i*)(d),a);d+=16;

Page 97: 組み込み関数(intrinsic)によるSIMD入門

色変換(2/11)cv::cvtColor(a,b,CV_BGR2Gray)

void cvtBGR2GraySSEShort(Mat& src Mat& dest)void cvtBGR2GraySSEShort(Mat& src, Mat& dest){

const int size = src.size().area()*src.channels();uchar* s = src.ptr<uchar>(0);uchar* d = dest.ptr<uchar>(0);//スプリ トをしてから RGBからグレイに変換//スプリットをしてから,RGBからグレイに変換//BBBBBBGGGGGRRRRRへシャッフル(分離)const m128i mask1 =const __m128i mask1   

_mm_setr_epi8(0,3,6,9,12,15,1,4,7,10,13,2,5,8,11,14);const __m128i smask1 = 

_mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15);const __m128i ssmask1 = t i8(11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10)_mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);

Page 98: 組み込み関数(intrinsic)によるSIMD入門

色変換(3/11)cv::cvtColor(a,b,CV_BGR2Gray)//GGGGGGBBBBBRRRRRへシャッフル//GGGGGGBBBBBRRRRRへシャッフルconst __m128i mask2 = 

_mm_setr_epi8(0,3,6,9,12,15, 2,5,8,11,14,1,4,7,10,13);//const __m128i smask2 = 

_mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,11,12,13,14,15); // k1と同じ//smask1と同じ

const __m128i ssmask2 = mm setr epi8(0 1 2 3 4 11 12 13 14 15 5 6 7 8 9 10);_mm_setr_epi8(0,1,2,3,4,11,12,13,14,15,5,6,7,8,9,10);

//RRRRRRGGGGGBBBBBへシャッフル//__m128i mask3 = 

_mm_setr_epi8(0,3,6,9,12,15, 2,5,8,11,14,1,4,7,10,13);//mask2と同じ

// t 128i k3//const __m128i smask3 = _mm_setr_epi8(6,7,8,9,10,0,1,2,3,4,5,6,7,8,9,10);//smask1と同じ

Page 99: 組み込み関数(intrinsic)によるSIMD入門

色変換(4/11)cv::cvtColor(a,b,CV_BGR2Gray)//ブレンドマスク//ブレンドマスクconst __m128i bmask1 = _mm_setr_epi8(255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0);const __m128i bmask2 = _mm_setr_epi8(255,255,255,255,255,255,255,255,255,255,255

0 0 0 0 0),0,0,0,0,0);const __m128i bmask3 = _mm_setr_epi8(255 255 255 255 255 0 0 0 0 0 0 0 0 0 0 0);(255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0);const __m128i bmask4 = _mm_setr_epi8(255,255,255,255,255,255,255,255,255,255,0,0,

0,0,0,0);

Page 100: 組み込み関数(intrinsic)によるSIMD入門

色変換(5/11)cv::cvtColor(a,b,CV_BGR2Gray)

//固定小数点で色変換の整数演算を定義//固定小数点で色変換の整数演算を定義const int shift = 8;const int amp = 1<<shift;const int _R_=(int)(amp*0.299);const int _G_=(int)(amp*0.587);

t i t B (i t)( *0 114)const int _B_=(int)(amp*0.114);const __m128i R = _mm_set1_epi16(_R_);const m128i G = mm set1 epi16( G );const __m128i G   _mm_set1_epi16(_G_);const __m128i B = _mm_set1_epi16(_B_);const __m128i zero = _mm_setzero_si128();

//48( 16x3)画素づつ処理for(int i=0;i<size;i+=48){{

Page 101: 組み込み関数(intrinsic)によるSIMD入門

色変換(6/11)cv::cvtColor(a,b,CV_BGR2Gray)

__m128i a = _mm_shuffle_epi8(_mm_load_si128((__m128i*)(s+i)),mask1);

__m128i b = _mm_shuffle_epi8(_mm_load_si128((__m128i*)(s+i+16)),mask2);

128i h ffl i8__m128i c = _mm_shuffle_epi8(_mm_load_si128((__m128i*)(s+i+32)),mask2);

const m128i aaaa = mm blendv epi8const __m128i aaaa = _mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask1),bmask2);

a = _mm_shuffle_epi8(a,smask1);b = _mm_shuffle_epi8(b,smask1);c = _mm_shuffle_epi8(c,smask1);

t 128i bbbb bl d i8const __m128i bbbb =_mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask3),bmask2);

Page 102: 組み込み関数(intrinsic)によるSIMD入門

色変換(7/11)cv::cvtColor(a,b,CV_BGR2Gray)

//頑張ってRRRRR GGGGG BBBBB を作る//頑張ってRRRRR.. GGGGG.. BBBBB..を作る__m128i a = _mm_shuffle_epi8

(_mm_load_si128((__m128i*)(s+i)),mask1);__m128i b = _mm_shuffle_epi8

(_mm_load_si128((__m128i*)(s+i+16)),mask2);128i h ffl i8__m128i c = _mm_shuffle_epi8

(_mm_load_si128((__m128i*)(s+i+32)),mask2);const m128i aaaa = mm blendv epi8const __m128i aaaa = _mm_blendv_epi8

(c,_mm_blendv_epi8(b,a,bmask1),bmask2);a = _mm_shuffle_epi8(a,smask1);b = _mm_shuffle_epi8(b,smask1);c = _mm_shuffle_epi8(c,smask1);

t 128i bbbb bl d i8const __m128i bbbb =_mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask3),bmask2);

Page 103: 組み込み関数(intrinsic)によるSIMD入門

色変換(8/11)cv::cvtColor(a,b,CV_BGR2Gray)

//頑張ってRRRRR GGGGG BBBBB を作る続き//頑張ってRRRRR.. GGGGG.. BBBBB..を作る続きa = _mm_shuffle_epi8(a,ssmask1);c = _mm_shuffle_epi8(c,ssmask1);b = _mm_shuffle_epi8(b,ssmask2);const __m128i cccc

bl d i8( bl d i8(b b k3)=_mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask3),bmask4);

__m128i a1 = _mm_unpackhi_epi8(aaaa,zero);

__m128i a2 = _mm_unpacklo_epi8(aaaa,zero);

1 ll i16( 1 B)a1 = _mm_mullo_epi16(a1,B);a2 = _mm_mullo_epi16(a2,B);

Page 104: 組み込み関数(intrinsic)によるSIMD入門

色変換(9/11)cv::cvtColor(a,b,CV_BGR2Gray)

//頑張ってRRRRR GGGGG BBBBB を作る続き//頑張ってRRRRR.. GGGGG.. BBBBB..を作る続きa = _mm_shuffle_epi8(a,ssmask1);c = _mm_shuffle_epi8(c,ssmask1);b = _mm_shuffle_epi8(b,ssmask2);const __m128i cccc

bl d i8( bl d i8(b b k3)=_mm_blendv_epi8(c,_mm_blendv_epi8(b,a,bmask3),bmask4);//完成//完成//aaaa:BBBB BBBB BBBB BBBB//bbbb:GGGGGGGGGGGGGGGG//cccc :RRRR RRRR RRRR RRRR

Page 105: 組み込み関数(intrinsic)によるSIMD入門

色変換(10/11)cv::cvtColor(a,b,CV_BGR2Gray)

//shortにアンパックして乗算上位 下位に分けて乗算//shortにアンパックして乗算上位,下位に分けて乗算__m128i a1 = _mm_unpackhi_epi8(aaaa,zero);

__m128i a2 = _mm_unpacklo_epi8(aaaa,zero);a1 = _mm_mullo_epi16(a1,B);a2 = _mm_mullo_epi16(a2,B);

128i b1 khi i8(bbbb )__m128i b1 = _mm_unpackhi_epi8(bbbb,zero);__m128i b2 = _mm_unpacklo_epi8(bbbb,zero);

b1 = mm mullo epi16(b1 G);b1   _mm_mullo_epi16(b1,G);b2 = _mm_mullo_epi16(b2,G);

__m128i c1 = _mm_unpackhi_epi8(cccc,zero);__m128i c2 = _mm_unpacklo_epi8(cccc,zero);

c1 = _mm_mullo_epi16(c1,R);2 ll i16( 2 R)c2 = _mm_mullo_epi16(c2,R);

Page 106: 組み込み関数(intrinsic)によるSIMD入門

色変換(11/11)cv::cvtColor(a,b,CV_BGR2Gray)

//半分づつに分けた値を加算//半分づつに分けた値を加算a1 = _mm_add_epi16(a1,b1);a1 = _mm_add_epi16(a1,c1);a2 = _mm_add_epi16(a2,b2);a2 = _mm_add_epi16(a2,c2);

//8bit シフトによる割り算固定小数点で8bitシフト//8bitsシフトによる割り算,固定小数点で8bitシフトa1 = _mm_srli_epi16(a1,8);a2 = mm srli epi16(a2 8);a2   _mm_srli_epi16(a2,8);

//shortのデータをパックしてストアa = _mm_packus_epi16(a1,a2);_mm_store_si128((__m128i*)(d),a);d+=16;

}}}

Page 107: 組み込み関数(intrinsic)によるSIMD入門

結果結果

O C 8 0• OpenCV 840.7 ms• C++ 519.4 ms  (固定小数点)• SSE 306.9 ms (アライメント無視)

• SSE 172.7 msSSE 172.7 msコメント

16ビットに桁を落としたので 精度が悪くなっているが– 16ビットに桁を落としたので,精度が悪くなっているが,かなりの高速化に成功

(JPEGなどの符号化など色変換でビットが落ちて画像品質が落ちると困( な の符号化な 色変換で ット 落ちて画像品質 落ちる 困るものには向かないが,画像処理の工程で色変換の精度が高くは必要のないものも多数)

Page 108: 組み込み関数(intrinsic)によるSIMD入門

新しく使ったその他関数一覧新しく使ったその他関数 覧

• Mullo

• BlendBlend

• Insert

• extract

Page 109: 組み込み関数(intrinsic)によるSIMD入門

乗算 mm mullo epi乗算_mm_mullo_epi

• _mm_mullo_epi(__m128i a, __m128i b)

• A,bの16bit同士の積を8つ同時に行う.桁のあふれは無視あふれは無視

• _mm_mulhi_epi(__m128i a, __m128i b)

が上位ビ 演算• が上位ビットの演算

Page 110: 組み込み関数(intrinsic)によるSIMD入門

挿入 インサート挿入 インサート

指定• 指定して代入

• _mm_insert_epi8(__m128i a,int b,int n);

• Aのn番目のレジスタに値bを書きこむAのn番目のレジスタに値bを書きこむ

Page 111: 組み込み関数(intrinsic)によるSIMD入門

抽出 エクストラクト抽出 エクストラクト

• int r = _mm_extract_epi8(__m128i a, int n)

• レジスタaの15番目の値を抽出してrに代入レジスタaの15番目の値を抽出してrに代入

Page 112: 組み込み関数(intrinsic)によるSIMD入門

マスク付き代入 ブレンドマスク付き代入 ブレンド

部分 制御• マスクの部分で代入のオンオフ制御

• _mm_blendv_epi8 (__m128 a, __m128 b, __m128 mask)

• Maskの値で出力をでaにするかbにするか制御できる

Page 113: 組み込み関数(intrinsic)によるSIMD入門

まとめまとめ

組 数 を使 ば組み込み関数(intrinsic)を使えばOpenCVを超える速度の実装がp 装

結構簡単に出来てしまう!?

※ただし,16の倍数に限る

※ただし では今日紹介した関数はほとんど で( 命令※ただし,IPPでは今日紹介した関数はほとんどSSEで(AVX命令を含む)実装されている

※O CVは全てのIPP関数をラップしているわけではないので※OpenCVは全てのIPP関数をラップしているわけではないので

Page 114: 組み込み関数(intrinsic)によるSIMD入門

Appendix

Page 115: 組み込み関数(intrinsic)によるSIMD入門

SIMDの歴史(Intel)SIMDの歴史(Intel)64bitレジスタ m6464bitレジスタ__m64• MMXMMX PentiumとかPentiumII (1997)

b レジスタ128bitレジスタ__m128• SSE Pentium III(1999):浮動小数点に対応• SSE2 Pentium 4(2000):整数,倍精度浮動小数点• SSE3 Pentium 4(2004)の後期:• SSSE3 Pentium 4(2006)の後期:• SSE4.1 Core 2 の前期(2007)前期( )• SSE4.2 Core 2 の後期,Core i7(2008)

256bitレジスタ m256256bitレジスタ__m256• AVX Core i7 Sandy Bridge(2011.1 ):浮動小数点に対応• AVX2? 整数が256bitに対応予定

Page 116: 組み込み関数(intrinsic)によるSIMD入門

備考備考

対応する バ ジ が な• CPUによって対応するSSEのバージョンが異なるため,CPUによっては使えない関数がある

• 結構便利な関数ほど上位のSSEのバージョンで定義されているで定義されている.

Page 117: 組み込み関数(intrinsic)によるSIMD入門

今回の関数のSSEの対応今回の関数のSSEの対応

• SSE

• SSE2SSE2

• SSE3

• SSSE3

• SSE4 1SSE4.1

• __m128i _mm_cvtepi8_epi16 (__m128i a) 

Page 118: 組み込み関数(intrinsic)によるSIMD入門

使っているCPUがどこまでSSEをサポートしているか

void checkCPU(void){

int CPUInfo[4];int InfoType = 1;int InfoType = 1;__cpuid(CPUInfo, InfoType);

if (CPUInfo[3] & 0x00800000) printf("support MMX¥n");else printf("DO NOT support MMX¥n");else printf( DO NOT support MMX¥n );if (CPUInfo[3] & 0x02000000) printf("support SSE¥n");else printf("DO NOT support SSE¥n");if (CPUInfo[3] & 0x04000000) printf("support SSE2¥n");else printf("DO NOT support SSE2¥n");else printf( DO NOT support SSE2¥n );if (CPUInfo[2] & 0x00000001) printf("support SSE3¥n");else printf("DO NOT support SSE3¥n");if (CPUInfo[2] & 0x00080000) printf("support SSE4.1¥n");else printf("DO NOTsupport SSE4 1¥n");else printf( DO NOTsupport SSE4.1¥n );if (CPUInfo[2] & 0x00100000) printf("support SSE4.2¥n");else printf("DO NOT support SSE4.2¥n");return ;

}

OpenCV.jpクックブックの「CPUがサポートする機能(SSEなど)をチェックする」よりhttp://opencv.jp/cookbook/opencv_utility.html#cpu‐sse

}

Page 119: 組み込み関数(intrinsic)によるSIMD入門

マニュアルhttp://software intel com/en us/avx/http://software.intel.com/en‐us/avx/

Page 120: 組み込み関数(intrinsic)によるSIMD入門

128 単精度• __m128 単精度• __m128d倍精度

整数• __m128i 整数

• _mm_op_suffix• S:シングル• D:ダブル• i# #={8,16,32,64,128}符号あり整数{ , , , , }符号あり整数• u# #={8,16,32,64,128}符号なし整数

Page 121: 組み込み関数(intrinsic)によるSIMD入門

キャッシュ制御 Streamキャッシュ制御 Stream

キ シ を使わずにメモリに読み書きする関数• キャッシュを使わずにメモリに読み書きする関数• _mm_stream_load_si128

i128• _mm_stream_si128

キ シ を汚したくな ときに使うキャッシュを汚したくないときに使う• 読みこんだデータはもう使わないとき

出力デ タをしばらく使わな とき• 出力データをしばらく使わないとき

• 画像データが大きくて書きこみをキャッシュに残さないほうがいいときとかうがいいときとか

– prefetch

Page 122: 組み込み関数(intrinsic)によるSIMD入門