組み込み関数(intrinsic) を用いた組み込み関数(intrinsic) を用いた
SIMD化による高速画像処理入門
名古屋工業大学 福嶋慶繁名古屋工業大学 福嶋慶繁
Twitter @fukushima1981
サンプルコードはこちら
https://github.com/norishigefukushima/IntrinsicsSample
整数の足し算整数の足し算
2進数表現2進数表現
実際は?(shortの例)実際は?(shortの例)
0000000000000011
++
00000000000001000000000000000100 
= ?
実際は?(shortの例)実際は?(shortの例)
0000000000000011
++
00000000000001000000000000000100 
= ?
もったいなくない?もったいなくない?
目標目標
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まで)
目的目的
を使えば 化は難しくないよ• 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 cmp
and cvtsc…
– コアな処理の関数ならIPP使えばAVXまで使った
最適化してあるよ(Linuxなら研究用は無料)
http://software.intel.com/en‐us/non‐
commercial‐software‐development
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)
先に結果先に結果
ピ 効果なしピ 効果なしコピー 効果なし
変換 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倍
通常のプログラミングでは,意識しな
いこと
レジスタレジスタ
一番CPUに近い記憶装置番CPUに近い記憶装置
ハード
ディスク
メモリ
L2
キャッシュ
L1
キャッシュ
レジスタ
演算回路
(+ー×÷)
SIMD演算ではもったいない部分を有効活用
できるレジスタを使う
m128__m128
128bitを記憶できるレジスタ(SSE2の場合)
アセンブラアセンブラ
プ ジ 開始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], xmm0
add     edi, 16
add     esi, 16
loop LL01loop    LL01
pop     ebp ; 引数処理:後述 ;
ret
mul asm endp ; プロシジャ終了 ;mul_asm endp ; プ シジャ終了 ;
http://www1.icnet.ne.jp/nsystem/simd_tobira/simd_cording.html
アセンブラアセンブラ
プ ジ 開始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], xmm0
add     edi, 16
add     esi, 16
loop LL01loop    LL01
pop     ebp ; 引数処理:後述 ;
ret
mul asm endp ; プロシジャ終了 ;mul_asm endp ; プ シジャ終了 ;
http://www1.icnet.ne.jp/nsystem/simd_tobira/simd_cording.html
Intrinsic関数Intrinsic関数
{• __asm { 
mov ch1, 0x41
mov ch2, 0x42 }, }
– とか魔法を唱えなくてもOK
言語 関数 た 使 る• C言語の関数みたいに使える
– 例:ucharが16個のデータ”a”をレジスタ”ra”にロード
uchar a[16];uchar a[16];
__m128i ra = _mm_load_si128((__m128i*)(a));
注意:自分でアセンブラを書いたほうが最適コードを
書くことが出来ます.
目次目次
基本編 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)
基本編
メモリからレジスタにロードメモリからレジスタにロ ド
レジスタ同士の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)
レジスタ( m128)レジスタ(__m128)
128 bitのレジスタを自由に切って使う128 bitのレジスタを自由に切って使う
128bit x 1
long double
64bit x 2
long , double
32bit x 4
int, float
16bit x 8
short
8bit x 16
char
SIMD演算SIMD演算
“1回の命令”で16個のデータを同時に足し算する例1回の命令 で16個のデータを同時に足し算する例
8bit x 16
char
8bit x 16
+ + + + + + + + + + + + + + + +
8bit x 16
char
8bit x 16
charchar
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];
}
}
ただの足し算
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);
}
}
実験結果(uchar)実験結果(uchar)
大きな配列(5Gb t )を入力して計算大きな配列(5Gbyte)を入力して計算
66 ms (SSE)( )
vs
770 ms (ベタ)
11 6倍の高速化11.6倍の高速化
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の足し算
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);
}
}
実験結果(short)実験結果(short)
大きな配列(5Gb t )を入力して計算大きな配列(5Gbyte)を入力して計算
120 ms (SSE)( )
vs
770 ms (ベタ)
6 4倍の高速化6.4倍の高速化
ロードとストア(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のデ タを
メモリーレジスタ間で転送する関数
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,15
shortのとき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
加算 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 足し算
他にも演算はたくさんある他にも演算はたくさんある
Add 加算Add 加算
Adds 飽和演算付き加算
減算Sub 減算
Subs 飽和演算付き減算
Avg 平均
Min 最小値
Max 最大値
Mul 乗算乗算
Sad 絶対誤差|a‐b|
アライメントアライメント
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で中身をつくるとアライメントがそろってます
アライメントによるの自由度の損失アライメントによるの自由度の損失
__m128i a = _mm_load_si128((__m128i*)(src));
_mm_store_si128((__m128i*)(dest),a);
src+=16,dest+=16など16区切りでしか処理できない...
メモリの区切りをずらした処理が出来ない...
アライメントがずれる場合アライメントがずれる場合
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);
}
}
この関数は落ちる
アライメントがずれる場合アライメントがずれる場合
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);
}
}
この関数は落ちない(すこし遅い)
応用編
型変換型変換
シャッフル
Stream書き込み
実際に画像処理をするときは実際に画像処理をするときは...
ジ タ ド• メモリからレジスタにロード(8bit)
• レジスタの型変換レジスタの型変換
• レジスタ同士のSIMD演算(16bit)
• レジスタの型変換
• レジスタからメモリにストア(8bit)レジスタからメモリにストア(8bit)
• など,ucharの計算だけではおさまらない....
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←一個余った...
• 処理が煩雑• 処理が煩雑
型変換型変換
さ ズ 大き ズ• 小さいサイズから大きいサイズへ
– uchar → short→
– uchar → int
• 大きいサイズから小さいサイズへ
– short→ uchar
int→uchar– int→uchar
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);
}
}}
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);
}
}
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);
}
}
比較比較
• C++:  355.772 ms
• アライメント無視: 64 8431 msアライメント無視:  64.8431 ms
• アライメント考慮1: 44.4918 ms
• アライメント考慮2: 37.3332 ms
最大9.5倍の高速化
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);
}}
}
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);
}
}
比較比較
• C++:  365.9 ms
• アライメント無視: 125 0 msアライメント無視: 125.0 ms
• アライメント考慮: 65.6 ms
最大5.6倍の高速化
変換 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
シフト 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)
レジスタ全体を右 左にシフトする関数レジスタ全体を右,左にシフトする関数
右シフトで任意のビットを下位に持ってこれる
個別のシフト mm slli epi32 (SSE2)個別のシフト_mm_slli_epi32 (SSE2)
0 0
_mm_slli_epi32(__m128i, 2) _mm_srli_epi32 (__m128i, 2)
si128が全体シフトとなるのに対して,
epi16,32,64など,個別に区切ってシフトも可能
整数シフト(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
アンパック 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
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にセットする関数
大きなものから小さなものへ大きなものから小さなものへ
• Short uchar
• Int ucharInt uchar
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);
}
}}
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);
}
}
実験結果実験結果
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 ms
8.2倍の高速化8.2倍の高速化
パック mm packsパック _mm_packs
_mm_packs_epi16
2つのレジスタの各値が飽和演算されながらつぶされ,
上位ビ ト 位ビ ト 整列される上位ビット下位ビットへ整列される
パックx2int x int →short パックx2int x int →short
_mm_packs_epi32
short x short →uchar
_mm_packs_epi16
データの並び替えデータの並び替え
な き 必• こんなときに必要
– 逆順ソート(フリップ)逆順ソ ( リッ )
– スワップ
RGBRGBRGB RRRGGGBBB– RGBRGBRGB RRRGGGBBB
などの並び替え
逆順ソート逆順ソート
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;
}}
}
比較比較
• C++ 50ms
• SSE 5.7msSSE 5.7ms
8 8倍高速化8.8倍高速化
シャッフル 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);
vs OpenCV 編
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)
実験条件実験条件
ま ポ• Intel Core i7 920 SSE4.2までサポート
• 2 66→2 93GHzまでオーバークロックして使用2.66→2.93GHzまでオ バ クロックして使用
• OpenCVの関数
• 最も簡単?と思われる実装を自前で(C++)最も簡単?と思われる実装を自前で(C++)
• SSEによる実装
の3つを比較
先に結果先に結果
ピ 効果なしピ 効果なしコピー 効果なし
変換 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倍
コピー 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バイト単位でロードとストアを
繰り返すだけ!繰り返すだけ!
結果結果
• OpenCV 68.2 ms
• C++ 308.4ms
• SSE 62.8 ms
コメント
– さすがにコピーは変わらない
– SSEを使ってでコピーしているところをmemcpyでpy
書いても同じ速度
– 自前でループ展開して代入するよりは速い.
変換 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が出来る関数だけど今回は無視)
結果結果
• OpenCV 159.2 ms
• C++ 206.5 msC++ 206.5 ms
• SSE 100.7 ms
コメントコメント
– 型変換はOpenCVに実装されてないため高速化さ
れるれる.
加算 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);
}}
}
結果結果
• OpenCV 101.1 ms
• C++ 524.9 msC++ 524.9 ms
• SSE 71.4 ms
コメントコメント
– 例外処理が無いことやアライメントをそろえている
分O CVの関数より高速分OpenCVの関数より高速
乗算(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));
乗算(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);
}}
}
結果結果
• OpenCV 405.8 ms
• C++ 409.3 msC++ 409.3 ms
• SSE 106.1 ms
コメントコメント
– OpenCVには実装されていない命令のためかなり
高速化に成功高速化に成功
変換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 int
l 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));
変換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);
}
}
結果結果
• OpenCV 1067.5 ms
• C++ 307.6 msC++ 307.6 ms
• SSE 109.9 ms
• コメントコメント
– convertToとかかなり使うのに,OpenCVはどんな
実装してるんだか実装してるんだか...
– 乗算と加算,型変換のSSE化なのでかなり速い
反転 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;
反転 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;
}
}
}
結果結果
フリップには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ように作ら
ないと使えないかなないと使えないかな
色変換色変換
問題 画素を16個ロ ドすると先頭は問題:画素を16個ロードすると先頭は...
RGBRGBRGBRGBRGB R 一個余る
戦略戦略
1. 最後のあまりを無視する
2 3個集めて 8個づつ処理すればあまりなし!2. 3個集めて48個づつ処理すればあまりなし!
RGBRGBRGBRGBRGBR
GBRGBRGBRGBRGBRG
swap
swap
BRGBRGBRGBRGBRGB
swap
色変換 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 B
for(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);
}
}
色変換高速版(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 BG
t 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)
{
色変換高速版(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);
色変換高速版(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);
}
}}
結果結果
• OpenCV 392.3 ms
• C++ 265.2 msC++ 265.2 ms
• SSE 99.3 ms
• SSE 68.8 ms (アライメントまで考慮)
コメントコメント
– 良く使う割にOpenCVの関数は遅い
分離 cv::split(a v)分離 cv::split(a,v)
素を 個 ドする 先問題:画素を16個ロードすると先頭は...
RGBRGBRGBRGBRGB R 一個余るRGBRGBRGBRGBRGB R 個余る
戦略
づ– 3個集めて48個づつ処理
RGBRGBRGBRGBRGBR RRRRRRRRRRRRRRRR
GBRGBRGBRGBRGBRG GGGGGGGGGGGGGGG
BRGBRGBRGBRGBRGB BBBBBBBBBBBBBBBB
16個づつに並びかえて書きこみ
分離(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回目シャッフル:BBBBBBGGGGGRRRRR
const __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
const __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回目シャッフル: GGGGGGBBBBBRRRRR
const __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回目シャッフル:BBBBBRRRRRGGGGGG
const __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;
}
}
分離(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
分離(3/8) cv::split(a v)分離(3/8) cv::split(a,v)
// Bを作る1本目の1回目シャッフル:BBBBBBGGGGGRRRRR
const __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回目シャッフル:GGGGGBBBBBBRRRRR
const __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回目シャッフル:RRRRRGGGGGBBBBBB
const __m128i ssmask1 = 
( )_mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);
分離(4/8) cv::split(a v)分離(4/8) cv::split(a,v)
// Bを作る2本目の1回目シャッフル: GGGGGGBBBBBRRRRR
const __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回目シャッフル:BBBBBRRRRRGGGGGG
const __m128i ssmask2 = 
( )_mm_setr_epi8(0,1,2,3,4,11,12,13,14,15,5,6,7,8,9,10);
分離(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); //同じマスク
があるの 代用があるので代用
分離(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)( ; ; )
{
分離(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);
分離(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;
}
}}
結果結果
• OpenCV 697.1 ms
• C++ 468.7 msC++ 468.7 ms
• SSE 171.4 ms
コメント
– RGBを並べ替えるのは結構めんどくさい...RGBを並 替えるのは結構めんどくさい...
– これベースにカラー画像のSSE化が可能
RRRRGGGGBBBBに並べ替えるのは 「速度的にこ– RRRRGGGGBBBBに並べ替えるのは,「速度的にこ
れってベストなの?」という疑問は残るが...
色変換 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をパックしてストア
色変換(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;
色変換(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);
色変換(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);//sm
ask1と同じ
色変換(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);
色変換(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)
{{
色変換(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);
色変換(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);
色変換(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);
色変換(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
色変換(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);
色変換(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;
}}
}
結果結果
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などの符号化など色変換でビットが落ちて画像品質が落ちると困( な の符号化な 色変換で ット 落ちて画像品質 落ちる 困
るものには向かないが,画像処理の工程で色変換の精度が高くは必
要のないものも多数)
新しく使ったその他関数一覧新しく使ったその他関数 覧
• Mullo
• BlendBlend
• Insert
• extract
乗算 mm mullo epi乗算_mm_mullo_epi
• _mm_mullo_epi(__m128i a, __m128i b)
• A,bの16bit同士の積を8つ同時に行う.桁の
あふれは無視あふれは無視
• _mm_mulhi_epi(__m128i a, __m128i b)
が上位ビ 演算• が上位ビットの演算
挿入 インサート挿入 インサート
指定• 指定して代入
• _mm_insert_epi8(__m128i a,int b,int n);
• Aのn番目のレジスタに値bを書きこむAのn番目のレジスタに値bを書きこむ
抽出 エクストラクト抽出 エクストラクト
• int r = _mm_extract_epi8(__m128i a, int n)
• レジスタaの15番目の値を抽出してrに代入レジスタaの15番目の値を抽出してrに代入
マスク付き代入 ブレンドマスク付き代入 ブレンド
部分 制御• マスクの部分で代入のオンオフ制御
• _mm_blendv_epi8 (__m128 a, __m128 b, __m128 mask)
• Maskの値で出力をでaにするかbにするか制御できる
まとめまとめ
組 数 を使 ば組み込み関数(intrinsic)を使えば
OpenCVを超える速度の実装がp 装
結構簡単に出来てしまう!?
※ただし,16の倍数に限る
※ただし では今日紹介した関数はほとんど で( 命令※ただし,IPPでは今日紹介した関数はほとんどSSEで(AVX命令
を含む)実装されている
※O CVは全てのIPP関数をラップしているわけではないので※OpenCVは全てのIPP関数をラップしているわけではないので
Appendix
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に対応予定
備考備考
対応する バ ジ が な• CPUによって対応するSSEのバージョンが異な
るため,CPUによっては使えない関数がある
• 結構便利な関数ほど上位のSSEのバージョン
で定義されているで定義されている.
今回の関数のSSEの対応今回の関数のSSEの対応
• SSE
• SSE2SSE2
• SSE3
• SSSE3
• SSE4 1SSE4.1
• __m128i _mm_cvtepi8_epi16 (__m128i a) 
使っている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
}
マニュアル
http://software intel com/en us/avx/http://software.intel.com/en‐us/avx/
128 単精度• __m128 単精度
• __m128d倍精度
整数• __m128i 整数
• _mm_op_suffix
• S:シングル
• D:ダブル
• i# #={8,16,32,64,128}符号あり整数{ , , , , }符号あり整数
• u# #={8,16,32,64,128}符号なし整数
キャッシュ制御 Streamキャッシュ制御 Stream
キ シ を使わずにメモリに読み書きする関数• キャッシュを使わずにメモリに読み書きする関数
• _mm_stream_load_si128
i128• _mm_stream_si128
キ シ を汚したくな ときに使うキャッシュを汚したくないときに使う
• 読みこんだデータはもう使わないとき
出力デ タをしばらく使わな とき• 出力データをしばらく使わないとき
• 画像データが大きくて書きこみをキャッシュに残さないほ
うがいいときとかうがいいときとか
– prefetch
組み込み関数(intrinsic)によるSIMD入門

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

  • 1.
    組み込み関数(intrinsic) を用いた組み込み関数(intrinsic) を用いた SIMD化による高速画像処理入門 名古屋工業大学 福嶋慶繁名古屋工業大学福嶋慶繁 Twitter @fukushima1981 サンプルコードはこちら https://github.com/norishigefukushima/IntrinsicsSample
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
    目標目標 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まで)
  • 7.
    目的目的 を使えば 化は難しくないよ• 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 cmp and cvtsc… – コアな処理の関数ならIPP使えばAVXまで使った 最適化してあるよ(Linuxなら研究用は無料) http://software.intel.com/en‐us/non‐ commercial‐software‐development
  • 8.
    MenuMenu ピピコピー a.copyTo(b) 変換 a.convertTo(b,CV16S) コピー 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)
  • 9.
    先に結果先に結果 ピ 効果なしピ 効果なしコピー効果なし 変換 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倍
  • 10.
  • 11.
    アセンブラアセンブラ プ ジ 開始mul_asmproc                    ; プロシジャ開始 ; 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], xmm0 add     edi, 16 add     esi, 16 loop LL01loop    LL01 pop     ebp ; 引数処理:後述 ; ret mul asm endp ; プロシジャ終了 ;mul_asm endp ; プ シジャ終了 ; http://www1.icnet.ne.jp/nsystem/simd_tobira/simd_cording.html
  • 12.
    アセンブラアセンブラ プ ジ 開始mul_asmproc                    ; プロシジャ開始 ; 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], xmm0 add     edi, 16 add     esi, 16 loop LL01loop    LL01 pop     ebp ; 引数処理:後述 ; ret mul asm endp ; プロシジャ終了 ;mul_asm endp ; プ シジャ終了 ; http://www1.icnet.ne.jp/nsystem/simd_tobira/simd_cording.html
  • 13.
    Intrinsic関数Intrinsic関数 {• __asm {  movch1, 0x41 mov ch2, 0x42 }, } – とか魔法を唱えなくてもOK 言語 関数 た 使 る• C言語の関数みたいに使える – 例:ucharが16個のデータ”a”をレジスタ”ra”にロード uchar a[16];uchar a[16]; __m128i ra = _mm_load_si128((__m128i*)(a)); 注意:自分でアセンブラを書いたほうが最適コードを 書くことが出来ます.
  • 14.
    目次目次 基本編 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)
  • 15.
  • 16.
    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)
  • 17.
  • 18.
  • 19.
  • 20.
    Intrinsicによる加算コードの最適化Intrinsicによる加算コードの最適化 id l ddd 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); } }
  • 21.
  • 22.
    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の足し算
  • 23.
    Intrinsicによる加算コードの最適化Intrinsicによる加算コードの最適化 id l ddd 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); } }
  • 24.
  • 25.
    ロードとストア(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のデ タを メモリーレジスタ間で転送する関数
  • 26.
  • 27.
    加算 mm addepi(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 足し算
  • 28.
    他にも演算はたくさんある他にも演算はたくさんある Add 加算Add 加算 Adds飽和演算付き加算 減算Sub 減算 Subs 飽和演算付き減算 Avg 平均 Min 最小値 Max 最大値 Mul 乗算乗算 Sad 絶対誤差|a‐b|
  • 29.
    アライメントアライメント 128i l di128(( 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で中身をつくるとアライメントがそろってます
  • 30.
  • 31.
    アライメントがずれる場合アライメントがずれる場合 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); } } この関数は落ちる
  • 32.
    アライメントがずれる場合アライメントがずれる場合 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); } } この関数は落ちない(すこし遅い)
  • 33.
  • 34.
    実際に画像処理をするときは実際に画像処理をするときは... ジ タ ド•メモリからレジスタにロード(8bit) • レジスタの型変換レジスタの型変換 • レジスタ同士のSIMD演算(16bit) • レジスタの型変換 • レジスタからメモリにストア(8bit)レジスタからメモリにストア(8bit) • など,ucharの計算だけではおさまらない....
  • 35.
    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←一個余った... • 処理が煩雑• 処理が煩雑
  • 36.
    型変換型変換 さ ズ 大きズ• 小さいサイズから大きいサイズへ – uchar → short→ – uchar → int • 大きいサイズから小さいサイズへ – short→ uchar int→uchar– int→uchar
  • 37.
    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); } }}
  • 38.
    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); } }
  • 39.
    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); } }
  • 40.
    比較比較 • C++:  355.772 ms •アライメント無視: 64 8431 msアライメント無視:  64.8431 ms • アライメント考慮1: 44.4918 ms • アライメント考慮2: 37.3332 ms 最大9.5倍の高速化
  • 41.
    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); }} }
  • 42.
    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); } }
  • 43.
    比較比較 • C++:  365.9 ms •アライメント無視: 125 0 msアライメント無視: 125.0 ms • アライメント考慮: 65.6 ms 最大5.6倍の高速化
  • 44.
    変換 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
  • 45.
    シフト mm sllisi128 (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) レジスタ全体を右 左にシフトする関数レジスタ全体を右,左にシフトする関数 右シフトで任意のビットを下位に持ってこれる
  • 46.
    個別のシフト mm slliepi32 (SSE2)個別のシフト_mm_slli_epi32 (SSE2) 0 0 _mm_slli_epi32(__m128i, 2) _mm_srli_epi32 (__m128i, 2) si128が全体シフトとなるのに対して, epi16,32,64など,個別に区切ってシフトも可能
  • 47.
    整数シフト(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
  • 48.
    アンパック 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
  • 49.
    0とアンパック0とアンパック 0  1  2  3  4  5  6  7   8 9 10 11 12 13 14 150 1 23 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にセットする関数
  • 50.
  • 51.
    short からucharshort からuchar void cvtshort2ucharAligned (short* src, uchar* dest, intsize) { 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); } }}
  • 52.
    int からucharint からuchar void cvtint2ucharAligned(int* src, uchar* dest, intsize) { 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); } }
  • 53.
    実験結果実験結果 Short to uchar • C++: 341.8mC++: 341.8 m • SSE: 35.4 ms 9 7倍の高速化 Int to ucahr 9.7倍の高速化 Int to ucahr • C++: 372.1 ms • SSE: 45.3 ms 8.2倍の高速化8.2倍の高速化
  • 54.
    パック mm packsパック_mm_packs _mm_packs_epi16 2つのレジスタの各値が飽和演算されながらつぶされ, 上位ビ ト 位ビ ト 整列される上位ビット下位ビットへ整列される
  • 55.
    パックx2int x int→short パックx2int x int →short _mm_packs_epi32 short x short →uchar _mm_packs_epi16
  • 56.
    データの並び替えデータの並び替え な き 必•こんなときに必要 – 逆順ソート(フリップ)逆順ソ ( リッ ) – スワップ RGBRGBRGB RRRGGGBBB– RGBRGBRGB RRRGGGBBB などの並び替え
  • 57.
    逆順ソート逆順ソート 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) { m128ia = 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; }} }
  • 58.
    比較比較 • C++ 50ms •SSE 5.7msSSE 5.7ms 8 8倍高速化8.8倍高速化
  • 59.
    シャッフル 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);
  • 60.
  • 61.
    MenuMenu ピピコピー a.copyTo(b) 変換 a.convertTo(b,CV16S) コピー 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)
  • 62.
    実験条件実験条件 ま ポ• Intel Core i7 920 SSE4.2までサポート •2 66→2 93GHzまでオーバークロックして使用2.66→2.93GHzまでオ バ クロックして使用 • OpenCVの関数 • 最も簡単?と思われる実装を自前で(C++)最も簡単?と思われる実装を自前で(C++) • SSEによる実装 の3つを比較
  • 63.
    先に結果先に結果 ピ 効果なしピ 効果なしコピー効果なし 変換 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倍
  • 64.
    コピー 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バイト単位でロードとストアを 繰り返すだけ!繰り返すだけ!
  • 65.
    結果結果 • OpenCV 68.2ms • C++ 308.4ms • SSE 62.8 ms コメント – さすがにコピーは変わらない – SSEを使ってでコピーしているところをmemcpyでpy 書いても同じ速度 – 自前でループ展開して代入するよりは速い.
  • 66.
    変換 a convertTo(bCV 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が出来る関数だけど今回は無視)
  • 67.
    結果結果 • OpenCV 159.2 ms •C++ 206.5 msC++ 206.5 ms • SSE 100.7 ms コメントコメント – 型変換はOpenCVに実装されてないため高速化さ れるれる.
  • 68.
    加算 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); }} }
  • 69.
    結果結果 • OpenCV 101.1 ms •C++ 524.9 msC++ 524.9 ms • SSE 71.4 ms コメントコメント – 例外処理が無いことやアライメントをそろえている 分O CVの関数より高速分OpenCVの関数より高速
  • 70.
    乗算(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));
  • 71.
    乗算(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); }} }
  • 72.
    結果結果 • OpenCV 405.8 ms •C++ 409.3 msC++ 409.3 ms • SSE 106.1 ms コメントコメント – OpenCVには実装されていない命令のためかなり 高速化に成功高速化に成功
  • 73.
    変換2(1/2) a convertTo(bCV 16U 5 10)変換2(1/2) a.convertTo(b,CV_16U,5,10) void convertTouchar2ushortMatSSE(Mat& src,Mat& dest, const int l 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));
  • 74.
    変換2(2/2) a convertTo(bCV 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); } }
  • 75.
    結果結果 • OpenCV 1067.5 ms •C++ 307.6 msC++ 307.6 ms • SSE 109.9 ms • コメントコメント – convertToとかかなり使うのに,OpenCVはどんな 実装してるんだか実装してるんだか... – 乗算と加算,型変換のSSE化なのでかなり速い
  • 76.
    反転 cv::flip(a b1)反転 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;
  • 77.
    反転 cv::flip(a bcode)全対応反転 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; } } }
  • 78.
    結果結果 フリップには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ように作ら ないと使えないかなないと使えないかな
  • 79.
    色変換色変換 問題 画素を16個ロ ドすると先頭は問題:画素を16個ロードすると先頭は... RGBRGBRGBRGBRGBR 一個余る 戦略戦略 1. 最後のあまりを無視する 2 3個集めて 8個づつ処理すればあまりなし!2. 3個集めて48個づつ処理すればあまりなし! RGBRGBRGBRGBRGBR GBRGBRGBRGBRGBRG swap swap BRGBRGBRGBRGBRGB swap
  • 80.
    色変換 cv::cvtColor(a bCV 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 B for(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); } }
  • 81.
    色変換高速版(1/3) cv::cvtColor(a bCV 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 BG t 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) {
  • 82.
    色変換高速版(1/3) cv::cvtColor(a bCV 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);
  • 83.
    色変換高速版(1/3) cv::cvtColor(a bCV 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); } }}
  • 84.
    結果結果 • OpenCV 392.3 ms •C++ 265.2 msC++ 265.2 ms • SSE 99.3 ms • SSE 68.8 ms (アライメントまで考慮) コメントコメント – 良く使う割にOpenCVの関数は遅い
  • 85.
    分離 cv::split(a v)分離cv::split(a,v) 素を 個 ドする 先問題:画素を16個ロードすると先頭は... RGBRGBRGBRGBRGB R 一個余るRGBRGBRGBRGBRGB R 個余る 戦略 づ– 3個集めて48個づつ処理 RGBRGBRGBRGBRGBR RRRRRRRRRRRRRRRR GBRGBRGBRGBRGBRG GGGGGGGGGGGGGGG BRGBRGBRGBRGBRGB BBBBBBBBBBBBBBBB 16個づつに並びかえて書きこみ
  • 86.
    分離(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回目シャッフル:BBBBBBGGGGGRRRRR const __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 const __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回目シャッフル: GGGGGGBBBBBRRRRR const __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回目シャッフル:BBBBBRRRRRGGGGGG const __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; } }
  • 87.
    分離(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
  • 88.
    分離(3/8) cv::split(a v)分離(3/8)cv::split(a,v) // Bを作る1本目の1回目シャッフル:BBBBBBGGGGGRRRRR const __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回目シャッフル:GGGGGBBBBBBRRRRR const __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回目シャッフル:RRRRRGGGGGBBBBBB const __m128i ssmask1 =  ( )_mm_setr_epi8(11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10);
  • 89.
    分離(4/8) cv::split(a v)分離(4/8)cv::split(a,v) // Bを作る2本目の1回目シャッフル: GGGGGGBBBBBRRRRR const __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回目シャッフル:BBBBBRRRRRGGGGGG const __m128i ssmask2 =  ( )_mm_setr_epi8(0,1,2,3,4,11,12,13,14,15,5,6,7,8,9,10);
  • 90.
    分離(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); //同じマスク があるの 代用があるので代用
  • 91.
    分離(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)( ; ; ) {
  • 92.
    分離(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);
  • 93.
    分離(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; } }}
  • 94.
    結果結果 • OpenCV 697.1 ms •C++ 468.7 msC++ 468.7 ms • SSE 171.4 ms コメント – RGBを並べ替えるのは結構めんどくさい...RGBを並 替えるのは結構めんどくさい... – これベースにカラー画像のSSE化が可能 RRRRGGGGBBBBに並べ替えるのは 「速度的にこ– RRRRGGGGBBBBに並べ替えるのは,「速度的にこ れってベストなの?」という疑問は残るが...
  • 95.
    色変換 cv::cvtColor(a bCV BGR2Gray)色変換 cv::cvtColor(a,b,CV_BGR2Gray) 戦略戦略 • 固定小数点をshortの精度で行う (精度は悪いけど 気にしない)– (精度は悪いけど,気にしない) • 48個ロードして,BGRをB16個G16個C16個に並び 変え変え • Shortにアンパックして,R,G,Bの固定小数点演 算算 • R,G,Bを加算 加算の結果を8ビットシフト• 加算の結果を8ビットシフト • Shortをパックしてストア
  • 96.
    色変換(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;
  • 97.
    色変換(2/11) cv::cvtColor(a,b,CV_BGR2Gray) void cvtBGR2GraySSEShort(Mat& srcMat& 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);
  • 98.
    色変換(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 setrepi8(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);//sm ask1と同じ
  • 99.
    色変換(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 00 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);
  • 100.
    色変換(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) {{
  • 101.
    色変換(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 ffli8__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);
  • 102.
    色変換(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);
  • 103.
    色変換(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);
  • 104.
    色変換(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
  • 105.
    色変換(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 b1khi 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);
  • 106.
  • 107.
    結果結果 O C 80• OpenCV 840.7 ms • C++ 519.4 ms  (固定小数点) • SSE 306.9 ms (アライメント無視) • SSE 172.7 msSSE 172.7 ms コメント 16ビットに桁を落としたので 精度が悪くなっているが– 16ビットに桁を落としたので,精度が悪くなっているが, かなりの高速化に成功 (JPEGなどの符号化など色変換でビットが落ちて画像品質が落ちると困( な の符号化な 色変換で ット 落ちて画像品質 落ちる 困 るものには向かないが,画像処理の工程で色変換の精度が高くは必 要のないものも多数)
  • 108.
  • 109.
    乗算 mm mulloepi乗算_mm_mullo_epi • _mm_mullo_epi(__m128i a, __m128i b) • A,bの16bit同士の積を8つ同時に行う.桁の あふれは無視あふれは無視 • _mm_mulhi_epi(__m128i a, __m128i b) が上位ビ 演算• が上位ビットの演算
  • 110.
    挿入 インサート挿入 インサート 指定•指定して代入 • _mm_insert_epi8(__m128i a,int b,int n); • Aのn番目のレジスタに値bを書きこむAのn番目のレジスタに値bを書きこむ
  • 111.
    抽出 エクストラクト抽出 エクストラクト •int r = _mm_extract_epi8(__m128i a, int n) • レジスタaの15番目の値を抽出してrに代入レジスタaの15番目の値を抽出してrに代入
  • 112.
    マスク付き代入 ブレンドマスク付き代入 ブレンド 部分制御• マスクの部分で代入のオンオフ制御 • _mm_blendv_epi8 (__m128 a, __m128 b, __m128 mask) • Maskの値で出力をでaにするかbにするか制御できる
  • 113.
    まとめまとめ 組 数 を使ば組み込み関数(intrinsic)を使えば OpenCVを超える速度の実装がp 装 結構簡単に出来てしまう!? ※ただし,16の倍数に限る ※ただし では今日紹介した関数はほとんど で( 命令※ただし,IPPでは今日紹介した関数はほとんどSSEで(AVX命令 を含む)実装されている ※O CVは全てのIPP関数をラップしているわけではないので※OpenCVは全てのIPP関数をラップしているわけではないので
  • 114.
  • 115.
    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に対応予定
  • 116.
    備考備考 対応する バ ジが な• CPUによって対応するSSEのバージョンが異な るため,CPUによっては使えない関数がある • 結構便利な関数ほど上位のSSEのバージョン で定義されているで定義されている.
  • 117.
    今回の関数のSSEの対応今回の関数のSSEの対応 • SSE • SSE2SSE2 •SSE3 • SSSE3 • SSE4 1SSE4.1 • __m128i _mm_cvtepi8_epi16 (__m128i a) 
  • 118.
    使っている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 }
  • 119.
    マニュアル http://software intel com/enus/avx/http://software.intel.com/en‐us/avx/
  • 120.
    128 単精度• __m128単精度 • __m128d倍精度 整数• __m128i 整数 • _mm_op_suffix • S:シングル • D:ダブル • i# #={8,16,32,64,128}符号あり整数{ , , , , }符号あり整数 • u# #={8,16,32,64,128}符号なし整数
  • 121.
    キャッシュ制御 Streamキャッシュ制御 Stream キシ を使わずにメモリに読み書きする関数• キャッシュを使わずにメモリに読み書きする関数 • _mm_stream_load_si128 i128• _mm_stream_si128 キ シ を汚したくな ときに使うキャッシュを汚したくないときに使う • 読みこんだデータはもう使わないとき 出力デ タをしばらく使わな とき• 出力データをしばらく使わないとき • 画像データが大きくて書きこみをキャッシュに残さないほ うがいいときとかうがいいときとか – prefetch