142
マルチコアを用いた 画像処理 名古屋工業大学 福嶋 慶繁 SSII2014 チュートリアル 1 資料中のコードはGithub上にアップロード https://github.com/norishigefukushima/SSII2014

マルチコアを用いた画像処理

Embed Size (px)

DESCRIPTION

2014年6月に開催されたSSII2014(http://www.ssii.jp/)のチュートリアル講演用資料です. 使用したコード等はこちら. https://github.com/norishigefukushima/SSII2014 アブストラクト 「CPUのクロック数が年月とともに増加する時代は終わり、プログラムの高速化をCPUの性能向上に任せることのできるフリーランチの時代は終わりを迎えています。しかしムーアの法則はいまだに続いており、CPUはマルチコア化、SIMD化という形で高性能化が続いています。本チュートリアルでは、計算コストの高い画像処理を高速化するために、CPUの能力をあますことなく引き出す、マルチコアプログラミング、SIMDプログラミングを解説します。」

Citation preview

Page 1: マルチコアを用いた画像処理

マルチコアを用いた画像処理

名古屋工業大学

福嶋慶繁

SSII2014チュートリアル

1

資料中のコードはGithub上にアップロード

https://github.com/norishigefukushima/SSII2014

Page 2: マルチコアを用いた画像処理

自己紹介

2

氏名 福嶋 慶繁所属 名古屋工業大学専門 3次元画像処理,画像符号化,並列化

コンピュテーショナルフォトグラフィe-mail fukushima ”at” nitech.ac.jp

twitter @fukushima1981

多視点データベース

SIMD画像処理プログラミング

Page 3: マルチコアを用いた画像処理

3

今後の課題として,GPUによる高速化があげられる.

こんなセンテンスを書いたことはありませんか?

Page 4: マルチコアを用いた画像処理

質問

そのアルゴリズム,本当にGPUで速くなりますか?

その手法,GPUで並列化できますか?

GPUは100倍速くなる魔法の言葉ではありません!

4

Page 5: マルチコアを用いた画像処理

発表の目的

•なぜ並列化する必要があるのか

•並列化プログラミングの理解

•並列化プログラムを書けるように

• CPUでもここまで速くなるんだという啓蒙活動

5

Page 6: マルチコアを用いた画像処理

6

24コア 100%

@Xeon X5690 3.47GHz

Page 7: マルチコアを用いた画像処理

背景なぜ並列化が必要なのか?

7

Page 8: マルチコアを用いた画像処理

ムーアの法則

8

集積回路上のトランジスタ数は

「18か月(=1.5年)ごとに倍なる」

Intel 4004

Intel Core i7

3960X

Core i7は初期CPU

35万個分

Page 9: マルチコアを用いた画像処理

クロック数の上限

ACMQueue, CPU DB: Recording Microprocessor History:

https://queue.acm.org/detail.cfm?id=2181798

CPUのクロック周波数は

2000年ごろの3 GHzで

ほぼ頭打ち

9

Page 10: マルチコアを用いた画像処理

集積化は続き,CPUはマルチコアへ

10

スパコンの計算性能

プログラマのフリーランチの終焉

マルチコアプログラミングが必須に

Sutter, Herb. "The free lunch is over: A fundamental turn

toward concurrency in software." Dr. Dobb’s Journal 30.3

(2005): 202-210.

Core i7

Page 11: マルチコアを用いた画像処理

さらに...ダークシリコン

•フリーランチ時代 ~2005• トランジスタ数はクロック数へ

•ホモジニアス・マルチコア時代 2005~20??• トランジスタ数はコア数へ

•へテロジニアスコアへ• 高いクロック数を持つコアと複数のコアを持つ低速なコアとの複合

11

理由:発熱,電力対策• トランジスタは搭載可能だが電力が足りない• 熱を抑制するために高クロック化が不可能• 不要なチップの部分の電源をオフに→ダークシリコン

• ターボブーストの理由

Page 12: マルチコアを用いた画像処理

GPU

12

Page 13: マルチコアを用いた画像処理

GPUとCPUの計算速度

13

GPUは年率1.7倍に集積度が向上

CPUよりも圧倒的に高速

http://michaelgalloy.com/2013/06/11/cpu-vs-gpu-performance.html

J.Y. Chen “GPU Technology Trends and Future

Requirements,” Proc. International Electron

Devices Meeting, Dec. 2009.

Page 14: マルチコアを用いた画像処理

スーパーコンピュータ 京

14スパコン:京 10,000,000 GFLOPS

スパコン性能ランキング1位@2011年11月

モバイル:ARM Cortex-A15 64 GFLOPS

CPU:Core i7 Haswell 224 GFLOPS

GPU:GeForce GTX TITAN 4,700 GFLOPS

Page 15: マルチコアを用いた画像処理

Why CPU?

1. GPUがついてるとは限らない

2. 消費電力

3. CPUで計算するほうが,

速いアルゴリズムも存在

15

Page 16: マルチコアを用いた画像処理

GPUがCPUより100倍速いという神話を暴くCPU-GPU間でのスループットの評価

• Lee, Victor W., et al. "Debunking the 100X GPU vs. CPU myth: an evaluation of throughput computing on CPU and GPU." ACM SIGARCH Computer Architecture News. Vol. 38. No. 3. ACM, 2010.

16

インテルからの反論

Page 17: マルチコアを用いた画像処理

論文の要約

100倍なんてことはない!

10倍程度だ!並列化が非常に利くアルゴリズムはGPUがより速い時代※ただし,驚くほど高速化されるわけでもない※FLOPSに注目→現在10倍程度の差

17

Page 18: マルチコアを用いた画像処理

CPUのGPU化?

Intel Xeon Phiインテル® Xeon Phi™ コプロセッサーは,最大 61 個のコアと 244 スレッドで構成され、最大 1.2 TFLOPS の演算性能を発揮します.ハードウェア、ソフトウェア,ワークロード,パフォーマンス、効率性における多様な要件に対応するため,さまざまな製品が用意されています.

18

Intel Xeon Phi 7120X

61コアのプロセッサ

1.2 GHz

512bit SIMD命令

※Titanは4TFlops

Page 19: マルチコアを用いた画像処理

並列化の知識並列化のための基礎知識と画像処理への応用

• アムダールの法則

• 粒度とオーバーヘッド

• フリンの分類

• デザインパターン

• 画像処理の分類

19

Page 20: マルチコアを用いた画像処理

並列化とは?

複数の演算処理を同時に行うことで,スループットやレイテンシの

改善を行うこと

複数のコアで計算すれば計算速度が数倍に

20

Page 21: マルチコアを用いた画像処理

並列化の基本事項

1.アムダールの法則

2.オーバーヘッド

3.粒度

4.スループットとレイテンシ

5.フリンの分類21

Page 22: マルチコアを用いた画像処理

アムダールの法則(Amdahl's law)

𝑆 =1

(1 − 𝑃) +𝑃𝑁

22

S:高速化率P:並列化率N:プロセッサ数

0

2

4

6

8

10

12

14

16

18

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

高速化率(倍)

プロセッサ数数

P=0.9

P=1.0

P=0.8

P=0.0P=0.7

Page 23: マルチコアを用いた画像処理

アムダールが示す限界

並列化不可

並列可能

並列化率 90%並列化率 10%

並列化率:並列化可能な部分

並列化不可

たとえ無限個のコアで並列化しても

並列化不可の部分は高速化不可能

並列化不可

並列可能

並列化不可

Page 24: マルチコアを用いた画像処理

オーバーヘッド

コアをたくさん使うほど

スレッドを多数生成するほど

オーバヘッドが生じる

1. 共有メモリのロック

2. データの分配(データのコピーは時間がかかる)

3. スレッドの生成,起動,同期

24

Page 25: マルチコアを用いた画像処理

画像処理の並列性

画像処理は高い並列性を有する

•画素

•ブロック

•フレーム

•シーケンス

25

任意の細かさで並列処理可能

粒度の設定

Page 26: マルチコアを用いた画像処理

粒度画素単位

ブロック単位

フレーム単位

シーケンス単位

26

粒度が細かいほどよく分解される.

→バランスよく計算できるがオーバーヘッドが大きい.

細粒度

(fine-grain)

画素

ブロック

フレーム

シーケンス 疎粒度

(coarse-grain)

Page 27: マルチコアを用いた画像処理

粒度の選び方

•粒度が細かい → 均等な負荷分散 オーバーヘッド大

•粒度が荒い → 負荷分散が不平等 オーバーヘッド小

1. スループットかレイテンシどちらかが大事か?2. 計算量のデータ依存性3. 分解可能かどうか?

考慮すべきポイント

Page 28: マルチコアを用いた画像処理

並列性能における最適化パラメータスループット:単位時間あたりにどれだけ出力できるか

• 最大の速度で出力し続けたい場合

• 映画で使うようなCGのレンダリング

• ビデオのエンコーディング(非リアルタイム)

• 実験データの生成

レイテンシ:出力するまで最大どれだけ待つか

• インタラクティブな操作がしたい場合

• ゲーム

• 画像補正・フォトショップ

• デバッグ28

※スループットを出すように並列化するほうが簡単

Page 29: マルチコアを用いた画像処理

粒度の選択結果Task

ATask B Task C Task D

無駄が多すぎ

O

Hオーバーヘッド

Task

A

Task B

Task C

Task D 無駄

無駄

無駄O

H

O

H

O

H

O

H

オーバーヘッド多すぎ

並列化

最適

AO

HB

O

HB

O

HC

O

HC

O

HC

O

HC

O

HD

O

H

AO

HB

O

HB

O

HC

O

HC

O

HC

O

HC

O

HD

O

H

AO

HB

O

HB

O

HC

O

HC

O

HC

O

HC

O

HD

O

H

AO

HB

O

HB

O

HC

O

HC

O

HC

O

HC

O

HD

O

H

AO

HB

O

HC

O

HC

O

H

AO

HB

O

HC

O

HC

O

H

BO

HC

O

HC

O

HD

O

H

BO

HC

O

HC

O

HD

O

H

Page 30: マルチコアを用いた画像処理

粒度の選択結果~パーティクルフィルタを例として~

30Result of particle filter by using MIST @Nagoya Univ.

重い

軽い

ブロックで分割した場合 粒子で分割した場合

細かすぎ

粒子を何個かまとめて処理するのがベスト

Page 31: マルチコアを用いた画像処理

本日のチュートリアル内容での分類

• グラフィカルモデル(中レイテンシ)

• 2次元画像ではなく,尤度の次元を加えた3次元ボクセル処理

• 並列化不可能な最適化アルゴリズムが多々

• 超解像処理(低レイテンシ)

• ブロック単位で並列化

• すべての処理が高く並列性

• 機械学習(レイテンシ無視,高スループット)

• 複数台のマシンで並列化

• マシン間でのデータ共有

31

Page 32: マルチコアを用いた画像処理

画像処理の並列化の分解例

1. 画素単位(SIMD,GPU)

2. ブロック単位(マルチコア)

3. フロー単位(マルチコア)

4. フレーム単位(スクリプトやgnu parallel)

5. データセット単位(計算機に処理を分散,batファイル)

32

※本プレゼンのスコープ

Page 33: マルチコアを用いた画像処理

フリンの分類

Single Instruction, Single Data stream (SISD)

Single Instruction, Multiple Data streams (SIMD)

Multiple Instruction, Single Data stream (MISD)

Multiple Instruction, Multiple Data streams (MIMD)

33

※GPUはMIMDだが,SIMD風に書くときに最大のパフォーマンスを発揮する演算機NVIDIAは,SIMT (Single Instruction, Multiple Thread)と呼称

SISD

• シングル

コア

SIMD

• SSE・AVX

GPU

MISD

• FPGA,H/W†

MIMD

• マルチ

コア

命令の並行度とデータの並行度に基づく4つの分類

†厳密には多段に適応するため,MISDではないという専門家の意見も

Page 34: マルチコアを用いた画像処理

フリンの分類(図解)

PU

命令

データ

PUPUPUPU

データ

命令

CPUCPU

CPU

命令

PUデータP

U

P

U

P

U

P

Uデータ

命令

SISD:逐次プログラム SIMD:一度の命令で複数

のデータ処理

MISD:対象外 MIMD:マルチスレッド

プログラミング31

Page 35: マルチコアを用いた画像処理

画像処理の並列化プログラミング

どのように画像処理を並列化実行するのか?

•粒度の選択

•データを並列

•命令を並列

35

パターンを学ぶ

Page 36: マルチコアを用いた画像処理

構造化並列プログラミング

原著Structured Parallel Programming: Patterns for Efficient ComputationMichael McCool (著), James Reinders (著), Arch Robison (著)

翻訳構造化並列プログラミング―効率良い計算を行うためのパターンマイケル・マックール (著), 菅原 清文 (翻訳), エクセルソフト (翻訳)

並列化のデザインパターン

効率のよい並列プログラムの形とパターンを示した最も詳しい教科書

36

Page 37: マルチコアを用いた画像処理

並列化プログラミングの並列パターン

37Structured Parallel Processing p.21

Page 38: マルチコアを用いた画像処理

画像処理で使う代表的なパターン

データ並列

•Map(4章)

•Stencil(7章)

•Reduction(5章)

•Scan (5章)

プロセス並列

• Fork-join(8章)

•Pile-line(9章)

38

Page 39: マルチコアを用いた画像処理

Map

39

基本形.

すべての要素を並列化し,要素ごと

に計算する.

各要素に依存関係無し.

Page 40: マルチコアを用いた画像処理

Stencil

40

Mapの発展形.

要素をまず集約した後,Mapのよう

に並列化し,要素ごとに計算する.

集約後に各要素に依存関係無し.

Page 41: マルチコアを用いた画像処理

Reduction

41

並列化しづらい形1

分割統治法により,サブ領域で計算

した後に,その計算結果を統合す

る.

各要素に依存関係有り.

Page 42: マルチコアを用いた画像処理

Scan

42

並列化しづらい形2

Reductionの処理をさらに多段に接

続した処理.この形になると無理な

並列化が必要か考えるところ.

各要素に依存関係大有り.

Page 43: マルチコアを用いた画像処理

Fork-join

43

基本形.

並列化はすべてFork-join.

タスクやフローを並列する基本形.

Page 44: マルチコアを用いた画像処理

Pile-line

44

Fork-joinの発展形.

多段式のパイプライン

Page 45: マルチコアを用いた画像処理

画像処理への分類の適用

• ポイントオペレータ (Map)• 閾値処理,LUT,色変換,アルファブレンド

• エリアオペレータ (Map)• 平滑化フィルタなどFIRフィルタ,モルフォロジ演算,

• 周波数変換 (Scan ?専門的な分解方法が存在)• フーリエ,サイン・コサイン,ウェーブレット

• 探索 (reduction)• 最大値,最小値,レジストレーション

• 応用(fork-join, pipe-line)• セグメンテーション,ステレオ対応,オプティカルフロー,SIFT...

45

Page 46: マルチコアを用いた画像処理

実装各パターンの具体的な実装方法

• OpenMP

• SIMD

46

Page 47: マルチコアを用いた画像処理

並列化の実装手段

• PCクラスタ

• MPI(Message Passing Interface)

•マルチコア並列化(MIMD)

• OpenMP, Intel TBB, Intel Clik++, Concurrency(MSVC), Grand Central Dispatch(Mac), C=CSTRIPES

• SIMD

• (X86: MMX,SSE, AVX, 3DNow!), (ARM:NEON)

• GPU

• Cuda, OpenCL, OpenACC

47

Page 48: マルチコアを用いた画像処理

OpenCV3.0 Roadmap

Proc. ICVS

高速な外部ライブラリによる高速化

並列化ライブラリ等による高速化

ベクトル演算(SIMD)による高速化(CPU,GPU)

48

Page 49: マルチコアを用いた画像処理

並列化の比較

工程数

自由度・速度

Open MP

Intel TBB

各種ライブラリ

ネイティブスレッド

49簡単 手順が複雑

速い自由

遅い不自由

SIMD並列化

Page 50: マルチコアを用いた画像処理

OpenMP#pragma omp parallel for

50

Page 51: マルチコアを用いた画像処理

加算 (Map)

51

void add(uchar* a, uchar* b, uchar* dest, int num){

for(int i=0;i<num;i++){dest[i] = a[i] + b[i];}

}

void add_omp (uchar* a, uchar* b, uchar* dest, int num){

#pragma omp parallel forfor(int i=0;i<num;i++){dest[i] = a[i] + b[i];}

}

#pragma omp parallel for

この一行を追加するだけでforループが並列化される

Page 52: マルチコアを用いた画像処理

ポイントオペレータ(Map)

下記処理は加算の部分a[i]+b[i]を書き直すだけ

• 四則演算

• その他の算術演算 min, max, exp, log,ガンマ,sin, cos, tan...

• 閾値処理

• テーブルを使った変換

• 色変換,

• アルファブレンドによるクロスディゾルブ

52

Page 53: マルチコアを用いた画像処理

画素の合計(Reduction)

53

float sum(float* src, int num)

{

float ret=0.0f;

for(int i=0;i<num;i++)

{

ret += src[i];

}

return ret;

}

float sum_omp (float* src, int num)

{

float ret=0.0f;

#pragma omp parallel for

for(int i=0;i<num;i++)

{

ret += src[i];

}

return ret;

}

すべての画素の総和を取る

単純に並列化してはいけない.

Page 54: マルチコアを用いた画像処理

Thread0

ret+=10

(ret:110)

Thread1

ret+=20

(ret:120)

Thread2

ret+=15

(ret:115)

Thread3

ret+=30

(ret:130)

画素の合計

54

float sum_omp_true (float* src, int num)

{

float ret=0.0f;

#pragma omp parallel for reduction(+:ret)

for(int i=0;i<num;i++)

{

ret += src[i];

}

return ret;

}

reduction(+:ret)が追加全ての計算終了時に各々のretを総和

グローバルな変数に非同期に演算を書ける場合データの読み込み,書き込み時に同期が取れない場合計算結果が保証されない.

グローバル

ret=100

ret=0

Thread0

ret+=10

(ret:10)

ret=0

Thread1

ret+=20

(ret:20)

ret=0

Thread2

ret+=15

(ret:15)

ret=0

Thread3

ret+=30

(ret:30)

グローバル

ret=100+75

Page 55: マルチコアを用いた画像処理

フィルタ(Stencil)

55

void boxfilter(float* src, float* dest, int w, int h, int r){

float normalize = 1.0f/(float)((2*r+1)*(2*r+1));for(int j=r;j<h-r;j++)//画像端を無視{

for(int i=r;i<w-r;i++){

float sum = 0.0f;for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){

sum+= src[w*(j+l) + i+l];}

}dest[w*j+i] = sum*normalize;

}}

}

4重ループの平滑化フィルタいろいろな場所を並列化可能

Page 56: マルチコアを用いた画像処理

フィルタ

56

void boxfilter_omp1(float* src, float* dest, int w, int h, int r){

float normalize = 1.0f/(float)((2*r+1)*(2*r+1));#pragma omp parallel forfor(int j=r;j<h-r;j++)//画像端を無視{

for(int i=r;i<w-r;i++){

float sum = 0.0f;for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){

sum+= src[w*(j+l) + i+l];}

}dest[w*j+i] = sum*normalize;

}}

}

行単位で並列化

Page 57: マルチコアを用いた画像処理

フィルタ

57

void boxfilter_omp2(float* src, float* dest, int w, int h, int r){

floatnormalize = 1.0f/(float)((2*r+1)*(2*r+1));for(int j=r;j<h-r;j++)//画像端を無視{

#pragma omp parallel forfor(int i=r;i<w-r;i++){

float sum = 0.0f;for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){

sum+= src[w*(j+l) + i+l];}

}dest[w*j+i] = sum*normalize;

}}

}

列単位で並列化

Page 58: マルチコアを用いた画像処理

フィルタ

58

void boxfilter_omp3(float* src, float* dest, int w, int h, int r){

floatnormalize = 1.0f/(float)((2*r+1)*(2*r+1));for(int j=r;j<h-r;j++){

for(int i=r;i<w-r;i++){

float sum = 0.0f;#pragma omp parallel for reduction(+:sum)for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){

sum+= src[w*(j+l) + i+l];}

}dest[w*j+i] = sum*normalize;

}}

}

カーネルの行単位で並列化

Page 59: マルチコアを用いた画像処理

フィルタ~並列位置での比較~

どこで並列化するのが一番速いのか?

•基本的には,一番外側のループ→行単位での並列化

•分割回数が多さ,リダクション演算など,

オーバーヘッドが大きいため

•高速化のためには可能な限り最大の粒度を持ったほうがよい

59

Page 60: マルチコアを用いた画像処理

IIRフィルタ(Scan)

60

void iirfilter(float* src, float* dest, int w, int h, float a)

{

float ia = 1.0f-a;

for(int j=1;j<h-1;j++)//画像端を無視{

for(int i=1;i<w-1;i++)

{

dest[w*j+i]=a*src[w*j+i] + ia* dest[w*j+(i-1)];

}

}

}

左の出力と自身をブレンドするIIRフィルタ

効率的に計算するにはScanの形が推奨されているが...

Page 61: マルチコアを用いた画像処理

IIRフィルタ

61

void iirfilter_omp1(float* src, float* dest, int w, int h, float a)

{

float ia = 1.0f-a;

for(int j=1;j<h-1;j++)//画像端を無視{

#pragma omp parallel for scan ?for(int i=1;i<w-1;i++)

{

dest[w*j+i]= a * src[w*j+i]

+ia * dest[w*j+(i-1)];

}

}

}

OpenMPにScanの実装はないため自分で実装する必要がある→

Page 62: マルチコアを用いた画像処理

IIRフィルタ(Scan→Map)

62

外側ループをMapの形にして処理※コア数が少ないから出来ること※GPUではScanの実装が必須

void iirfilter_omp2(float* src, float* dest, int w, int

h, float a)

{

float ia = 1.0f-a;

#pragma omp parallel for

for(int j=1;j<h-1;j++)//画像端を無視{

for(int i=1;i<w-1;i++)

{

dest[w*j+i]=a * src[w*j+i]

+ ia * dest[w*j+(i-1)];

}

}

}

依存関係のない並列化

Thread 1

Thread 2

Thread 3

Thread 4

Page 63: マルチコアを用いた画像処理

Fork-join•いろいろなフィルタ出力を行う場合

• ガウシアンフィルタ

• ボックスフィルタ

• ソーベルフィルタ

をそれぞれ出力する場合

63

void forkjoin_ex(float* src, float* dest0, float* dest1, float*

dest2, int w, int h, int r)

{

Gaussianfilter(src,dest0,w,h,r);

Sobelfilter(src,dest1,w,h,r);

boxfilter(src,dest0,w,h,r);

}

void forkjoin_ex_omp(float* src, float* dest0, float*

dest1, float* dest2, int w, int h, int r)

{

#pragma omp parallel sections

{

#pragma omp section

{

Gaussianfilter(src,dest0,w,h,r);

}

#pragma omp section

{

Sobelfilter(src,dest1,w,h,r);

}

#pragma omp section

{

boxfilter(src,dest0,w,h,r);

}

}

}

Page 64: マルチコアを用いた画像処理

Pile-line

•ステレオマッチング• 画素単位のコスト計算

• コスト集約(フィルタリング)

• 最適化

• ポストフィルタ

• SIFT• DoG

• ローカライズ

• オリエンテーション

• ディスプリプション

64

Thread 1

Thread 2

Thread 3

Thread 4

もっとも簡単な並列化は画像4つ集めてMap処理.ただしレイテンシが大きい

Page 65: マルチコアを用いた画像処理

Pile-line

65

Thread 1

Thread 2

Thread 3

Thread 4

レイテンシも考慮して,入力・出力するただし,OpenMPで作るのは難しい

Page 66: マルチコアを用いた画像処理

OpenMP vs Intel TBB

•計算速度: Intel TBB > OpenMP

•対応パターン: Intel TBB >> OpenMP

•実装しやすさ: Intel TBB << OpenMP

•習得の速さ: Intel TBB < OpenMP

OpenMPは簡単,TBBはパフォーマンスが高い

66

Page 67: マルチコアを用いた画像処理

OpenCVにおけるスレッド並列化

• Intel TBBの並列化を参考にしてさまざまなバックエンドで動作可能なように拡張

• Intel TBB, OpenMP, MS PPL, Grand Central Dispatch(Mac), C=CSTRIPES

• ラムダ式でも代用可

67

class addInvorker : public cv::ParallelLoopBody{private:Mat *im1,*im2, *dst;public:

addInvorker(Mat& src1, Mat& src2, Mat& dest_): im1(&src1), im2(&src1), dst(&dest_){;}virtual void operator()( const cv::Range &r ) const{

const int width = im1->cols;for(int j=r.start;j<r.end;j++){

float* s1 = im1->ptr<float>(j);float* s2 = im2->ptr<float>(j);float* d = dst->ptr<float>(j);for(int i=0;i<width;i++){

d[i]= s1[i]+s2[i];}

}}};

void addParallelOpenCV(Mat& src1, Mat& src2, Mat& dest){addInvorker body(src1,src2,dest);cv::parallel_for_(Range(0, dest.rows), body);}

クラスを呼び出すだけ

Page 68: マルチコアを用いた画像処理

SIMD (SSE, AVX)__m128i ma = _mm_load_si128((const __m128i*)(a+i));

__m128i mb = _mm_load_si128((const __m128i*)(b+i));

ma = _mm_add_epi8(ma,mb);

_mm_store_si128((__m128i*)(dest+i), ma);68

Page 69: マルチコアを用いた画像処理

SSE, AVXによるSIMD演算

ひとつの命令で複数のデータを一度に処理可能→まとめた分だけ処理速度向上

SSE(少し前のCPUは対応)

• char x16, short x8, int x4, float x4, double x2

AVX(最近のCPUが対応)

• char x32, short x16, int x8, float x8, double x4

intrinsic 命令を使えばアセンブラの記述も不要

メモリ上のデータをSIMD演算用レジスタにロードする必要性

69

Page 70: マルチコアを用いた画像処理

SIMDレジスタを指す変数 __m128, __m256

128bit(SSE) や256bit(AVX)レジスタを自由に切って使用• 整数

• __m128i, __m256i

char, short, int, long に明示的な区別はないので使用に注意が必要

• 単精度小数点• __m128, __m256

4倍速,8倍速

• 倍精度小数点• __m128d, __m256d

• 2倍速,4倍速

70

Page 71: マルチコアを用いた画像処理

128bitレジスタのイメージ

71

Page 72: マルチコアを用いた画像処理

SIMD演算

72

Page 73: マルチコアを用いた画像処理

加算(MAP)

73

void add(uchar* a, uchar* b, uchar* dest, int num)

{

for(int i=0;i<num;i++)

{

dest[i] = a[i] + b[i];

}

}

元は,たった3行

Page 74: マルチコアを用いた画像処理

加算(MAP) uchar

74

void add_sse_uchar(uchar* a, uchar* b, uchar* dest, int num)

{

for(int i=0;i<num;i+=16)

{//メモリ上の配列A,Bを各をレジスタへロード

__m128i ma = _mm_load_si128((const __m128i*)(a+i));

__m128i mb = _mm_load_si128((const __m128i*)(b+i));

//A,Bが保持されたレジスタの内容を加算してmaのレジスタにコピー

ma = _mm_add_epi8(ma,mb);

//計算結果のレジスタ内容をメモリ(dest)にストア

_mm_store_si128((__m128i*)(dest+i), ma);

}

}

必要な関数を呼び出すだけ!

レジスタの管理も不要

GPUのように,メモリのロード・ストアが必要(ただし,非常に高速)

16個づつ処理→ループアンロール

たった5行?

Page 75: マルチコアを用いた画像処理

メモリのアライメント

• _mm_load_si128, _mm_store_si128

実は,この関数はメモリの番地が16バイト境界にそろっていないとプログラムが落ちます.代わりに下記関数を使えば,そのような制約は発生しません.ただし少々実行速度が遅いです.

• _mm_loadu_si128, _mm_storeu_si128

75

アドレス0番地 16番地 32番地 48番地 64番地

OSはある定数倍の区切りを先頭にしたメモリのコピーしか出来ない

ほしい場所

実際コピーした場所

ほしい場所

実際コピーした場所

Page 76: マルチコアを用いた画像処理

メモリのアライメントのそろえ方

原始的な方法1. メモリを多めに確保します.

2. 適切な境界まで先頭ポインタをずらして使います.

3. ずらしたポインタを戻して開放します.

76

現在の新しいVisual Studio中ではこうなってるので実はどっちを使っても大丈夫.#define _mm_free(a) _aligned_free(a)#define _mm_malloc(a, b) _aligned_malloc(a, b)

ダメ絶対!

この関数を使ってください. bに揃ええたいバイト数を入れれば調整してくれます.・Visual Studio: _aligned_malloc(a, b), _aligned_free(a)

・gcc: _mm_malloc(a, b) , _mm_free(a)

Page 77: マルチコアを用いた画像処理

加算(MAP) float

77

関数のsi128がpsに変換しただけ!

4個づつ処理→ループアンロール

void add_sse_float(float* a, float* b, float* dest, int num){

for(int i=0;i<num;i+=4){//メモリ上の配列A,Bを各をレジスタへロード__m128 ma = _mm_load_ps((a+i));__m128 mb = _mm_load_ps((b+i));//A,Bが保持されたレジスタの内容を加算してmaのレジスタにコピー

ma = _mm_add_ps(ma,mb);//計算結果のレジスタ内容をメモリ(dest)にストア_mm_store_ps((dest+i), ma);}

}

Page 78: マルチコアを用いた画像処理

加算(MAP) float - AVX

78

SSEに比べてさらに倍の速度_mm128 → _mm256に変わっただけXeon phiのAVX512を使えばそのさらに倍も可能

8個づつ処理→ループアンロール

void add_avx_float(float* a, float* b, float* dest, int num){

for(int i=0;i<num;i+=8){//メモリ上の配列A,Bを各をレジスタへロード__m256 ma = _mm256_load_ps((a+i));__m256 mb = _mm256_load_ps((b+i));//A,Bが保持されたレジスタの内容を加算してmaのレジスタにコピー

ma = _mm256_add_ps(ma,mb);//計算結果のレジスタ内容をメモリ(dest)にストア

_mm256_store_ps((dest+i), ma);}

}

Page 79: マルチコアを用いた画像処理

SIMD演算の関数例

add

•加算

Sub

•減算

Mul

•乗算

Div

•除算

Abs

•絶対値

Avg

•平均値

Dp

•内積

Floor

•切り捨て

Ceil

•切り上げ

Addsub

•交互に

加算,減算

Hadd

•要素間加算

Hsub

•要素間加算

Psadbw

• SAD計算

Cmp

•比較演算

Sqr

•平方根キャスト ビット演算

Popcnt

• ビット数上げ

79

これらの演算は明示的に使うと通常の関数を使うよりも高速化

Page 80: マルチコアを用いた画像処理

画素値の合計(Reduction)

80

float sum(float* src, int num){

float ret=0.0f;for(int i=0;i<num;i++){

ret += src[i];}return ret;

}

float sum2(float* src, int num)

{

float ret0=0.0f;

float ret1=0.0f;

float ret2=0.0f;

float ret3=0.0f;

for(int i=0;i<num;i+=4)

{

ret0 += src[4*i+0];

ret1 += src[4*i+1];

ret2 += src[4*i+2];

ret3 += src[4*i+3];

}

return ret0+ret1+ret2+ret3;

}

Page 81: マルチコアを用いた画像処理

画素値の合計(Reduction)

81

float sum_sse_float(float* src, int num)

{

__m128 tms = _mm_setzero_ps();

for(int i=0;i<num;i+=4)

{

__m128 ms = _mm_load_ps(src+i);tms = _mm_add_ps(tms,ms);

}

float data[4];

_mm_storeu_ps(data,tms);

return (data[0]+data[1]+data[2]+data[3]);

}

4単位で合計計算し,最後にその単位ごとに合計する.(最後のreduction計算はhaddでも可)

Page 82: マルチコアを用いた画像処理

フィルタ(Stencil)

82

void boxfilter_sse(float* src, float* dest, int w, int h, int r){

for(int j=r;j<h-r;j++)//画像端を無視{

floatnormalize = 1.0f/(float)((2*r+1)*(2*r+1));__m128 mnormalize = _mm_set1_ps(normalize);for(int i=r;i<w-r;i+=4)//4画素ごとに処理{

__m128 msum = _mm_setzero_ps();for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){__m128 ms=_mm_loadu_ps(src+w*(j+l) + i+l);

msum = _mm_add_ps(msum,ms);}

}msum = _mm_mul_ps(msum,mnormalize);_mm_storeu_ps(dest+w*j+i,msum);

}}

}

列単位に並列化カーネル単位に並列化はしない・カーネルサイズがSIMD幅に依存する・リダクション処理が必要

4画素づつ平均を一度に求めている.

Page 83: マルチコアを用いた画像処理

OpenMPとSIMDは併用可能

83

void boxfilter_sse_omp(float* src, float* dest, int w, int h, int r){

#pragma omp parallel forfor(int j=r;j<h-r;j++)//画像端を無視{

floatnormalize = 1.0f/(float)((2*r+1)*(2*r+1));__m128 mnormalize = _mm_set1_ps(normalize);for(int i=r;i<w-r;i+=4){

__m128 msum = _mm_setzero_ps();for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){__m128 ms=_mm_loadu_ps(src+w*(j+l)+i+l);

msum = _mm_add_ps(msum,ms);}

}msum = _mm_mul_ps(msum,mnormalize);_mm_storeu_ps(dest+w*j+i,msum);

}}

}

画像の行をOpenMPで並列化

画像の列の処理をSIMDで並列化

Page 84: マルチコアを用いた画像処理

IIRフィルタ(Scan→Map)

84

void iirfilter2(float* src, float* dest, int w, int h, float a){

float* srct = new float[w*h];transpose(src,srct);float ia = 1.0f-a;for(int i=1;i<w-1;i++){

for(int j=1;j<h-1;j+=4){srct[w*i+j+0]=a*src[w*i+j] + ia* srct[w*(i-1)+(j+0)];srct[w*i+j+1]=a*src[w*i+j] + ia* srct[w*(i-1)+(j+1)];srct[w*i+j+2]=a*src[w*i+j] + ia* srct[w*(i-1)+(j+2)];srct[w*i+j+3]=a*src[w*i+j] + ia* srct[w*(i-1)+(j+3)];}

}transpose(srct,dest);delete[] srct;

}

転置

Page 85: マルチコアを用いた画像処理

IIRフィルタ(Scan)

85

IIRフィルタをScanの形で表現すると並列化しないほうが速いほどのコスト

転置することでMapの形に変形する

void iirfilter_sse(float* src, float* dest, int w, int h, float a){

float* srct = new float[w*h];transpose(src,srct);float ia = 1.0f-a;const __m128 ma = _mm_set1_ps(a);const __m128 mia = _mm_set1_ps(ia);for(int i=1;i<w-1;i++){

for(int j=1;j<h-1;j+=4){__m128 ms0 = _mm_loadu_ps(&src[w*i+j]);__m128 ms1 = _mm_loadu_ps(&src[w*(i-1)+j]);ms0 = _mm_mul_ps(ms0,ma);ms1 = _mm_mul_ps(ms1,mia);ms0 = _mm_add_ps(ms0,ms1);_mm_storeu_ps(&src[w*i+j],ms0);}

}transpose(srct,dest);delete[] srct;

}

Page 86: マルチコアを用いた画像処理

SIMDのデメリット

ひとつの命令で複数のデータを一度に処理可能しかし,言い換えれば,

•要素の移動が煩雑

処理単位が決まっており,複数の命令を発行して移動する必要がある

•要素毎の命令が出来ない

複数回の計算結果をブレンドすることで実現可能なため非効率

•要素間の命令の効率が悪い

hadd(要素間を足して出力)など,簡単な命令ならある

86

Page 87: マルチコアを用いた画像処理

各要素を移動させる命令は非常に制約のある操作が必要

•要素ごとに値を代入したりコピーしたりする命令

メモリを経由するため,この操作は非常に重たい

• insert(代入), extract(コピー)

•各要素をレジスタ内,レジスタ間で移動する命令

非常に速いが操作が複雑

• unpack,pack ,blend,shuffle

87

Page 88: マルチコアを用いた画像処理

_mm_unpack

88

Page 89: マルチコアを用いた画像処理

_mm_packs

89

Page 90: マルチコアを用いた画像処理

逆順で出力を指定前二つがb側後ろ2つがa側

0 1 2 3 4 5 6 7__m128 a

3 2 5 4

__m128 b

__m128 c

_MM_SHUFFLE(0,1,2,3)

__m128 c = _mm_shuffle_ps(a,b,_MM_SHUFFLE(0,1,2,3))意味: bの0番目の要素を最後に,bの1番目の要素を後ろから2番目に,

aの2番目の要素を先頭から2番目に,bの3番目の要素を先頭にシャフル

_mm_shuffle

Page 91: マルチコアを用いた画像処理

Mask (0011->3)

_mm_blend

91

逆順で出力を指定前二つがb側後ろ2つがa側

0 1 2 3 4 5 6 7

__m128 a

0 1 6 7

__m128 b

__m128 c

__m128 c = _mm_blend_ps(a,b,mask)意味: bの0番目の要素を最後に,bの1番目の要素を後ろから2番目に,

aの2番目の要素を先頭から2番目に,bの3番目の要素を先頭にシャフル

Page 92: マルチコアを用いた画像処理

SIMDプログラミングの肝

•粒度が細かい計算向き

• OpenMPなどマルチコア並列化と併用可能

•要素間の計算が必要ない形に変換する

• shuffle,blendなどでデータをそろえる

92

Page 93: マルチコアを用いた画像処理

図解

93

0 1 2 3

4 5 6 7

__m128 a

__m128 b

ベクトル間の演算は得意

要素同士の演算は苦手

各要素を別々に処理するには,複数回の命令を実行して,必要なところを残して他を捨てる必要

ベクトル間のデータの並べかえは大変

Page 94: マルチコアを用いた画像処理

OpenMP 4.0

OpenMPがSIMDによるベクトル化に対応

•マルチスレッドだけ(今まで)

#pragma omp parallel for

• SIMDだけ(4.0から)

#pragma omp parallel simd

• SIMDとスレッド(4.0から)

#pragma omp parallel for simd

94

Visual Studioは今のところOpenMP2.0まで対応Intel compilerかgccが対応

Page 95: マルチコアを用いた画像処理

並列化の効果が少ない場合

•並列化の効果が少ない場合• IIRフィルタ• (アトミック命令が必要)ヒストグラムの生成

• インテグラルイメージの作成• 値を合計する

•並列化が不能~難しい• 動的計画法• エントロピー符号化 など前状態に強く依存する処理

•繰り返しが多い処理• PDE (partial differential

equations)• ニュートン法• Fork-joinが増える→オーバーヘッド増

•疎行列の処理• メモリが非連続→メモリ律速• メモリが許すならGPUへ• 密行列の処理は非常に向いてる

95

Page 96: マルチコアを用いた画像処理

モバイル端末では?

• android• OpenMP• TBB• ARM SIMD NEON

• iPhone• NEON• Grand Central Dispatch

• OpenMP ?

• Intel TBB ?

• GPGPU on Mobile Devices• OpenCL

96

#include <arm_neon.h>

void add9 (uint8x16_t *vec_in)

{

/* set sixteen elements of vec_9 to 9 */

uint8x16_t vec_9 = vmovq_n_u8(9);

/* add 9 to 16 vector elements using a NEON instruction */

*vec_in = vaddq_u8(*vec_in, vec_9);

}

SIMD NEON64bit幅(MMXと同じ),128bit幅 (SSEと同じ)関数名が違うだけでほぼ文法は同じ

Page 97: マルチコアを用いた画像処理

実は重要なメモリのIO

97

Page 98: マルチコアを用いた画像処理

記憶域へのアクセス

L1

L2

メモリ

HDD

ネットワーク

98

メモリの読み書きは高速だと思っていませんか?

実は下手な計算よりも重たいです.

Page 99: マルチコアを用いた画像処理

計算 vs メモリアクセス

1.すべての画素をコピーする

2.すべての画素に1を加算する

3.すべての画素に10を乗算する

実はこの処理の計算速度は

ほとんど変わりません

99

Page 100: マルチコアを用いた画像処理

ボトルネックはCPU?それともIO?

• CPU律速,メモリ律速

•データの込みこみ

•計算

•データの書き込み

100

メモリのパフォーマンスは6年で2倍

Hennessy & Patterson, Computer

Architecture, Morgan Kaufmann,2006

画像処理の場合,かなりのケースでメモリのIOがボトルネックSIMDによるベクトル化を行うと,4倍速8倍速以上とベクトル長以上にコードが高速化するのはこのボトルネックを解消しているのが要因

Page 101: マルチコアを用いた画像処理

GPU・CPUのメモリIO速度

101

メモリのIO 計算能力

Page 102: マルチコアを用いた画像処理

SRAM vs DRAM

• SRAM (キャッシュ)• 速い (L1 キャッシュ→1CPUサイクル)

• 小容量

• 高い

•DRAM(主記憶)• 遅い(100CPUサイクル)

• 大容量

• 安い

102

Page 103: マルチコアを用いた画像処理

メモリアクセスの最適化

メモリアクセスで重要なポイント• 局所性

• アライメント

• アクセス順序

例題:行列の転置

•キャッシュブロッキング• キャッシュに収まるようにデータ構造をブロッキングすること

103

Page 104: マルチコアを用いた画像処理

キャッシュブロッキング ~行列転置~

104

行メジャーなレイアウトを仮定

メモリのロードは1要素づつではなく幅を持って一度にキャッシュに格納される

一度キャッシュに入ったらロードの無駄(キャッシュミス)を防ぐように処理するのが最善

例えば4画素づつメモリにロード

Page 105: マルチコアを用いた画像処理

キャッシュブロッキング ~行列転置~

105

ブロック単位で転置

対角以外をスワップ

Page 106: マルチコアを用いた画像処理

ソートによる行列転置

106

1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16

1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16

1 2 5 6 9 10 13 14 3 4 7 8 11 12 15 16

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

#define _MM_TRANSPOSE4_PS(row0, row1, row2, row3) {¥__m128 tmp3, tmp2, tmp1, tmp0; ¥

¥tmp0 = _mm_shuffle_ps((row0), (row1), 0x44); ¥tmp2 = _mm_shuffle_ps((row0), (row1), 0xEE); ¥tmp1 = _mm_shuffle_ps((row2), (row3), 0x44); ¥tmp3 = _mm_shuffle_ps((row2), (row3), 0xEE); ¥

¥(row0) = _mm_shuffle_ps(tmp0, tmp1, 0x88); ¥(row1) = _mm_shuffle_ps(tmp0, tmp1, 0xDD); ¥(row2) = _mm_shuffle_ps(tmp2, tmp3, 0x88); ¥(row3) = _mm_shuffle_ps(tmp2, tmp3, 0xDD);} ¥

Page 107: マルチコアを用いた画像処理

転置の実装例

107

void transpose_sse_omp(float* src, float* dest, int w, int h)

{const int ww = 2*w;const int www = 3*w;#pragma omp parallel forfor(int j=0;j<h;j+=4){

float* s = src+w*j;for(int i=0;i<w;i+=4){__m128 m0 = _mm_load_ps(s+i);__m128 m1 = _mm_load_ps(s+w+i);__m128 m2 = _mm_load_ps(s+ww+i);__m128 m3 = _mm_load_ps(s+www+i);_MM_TRANSPOSE4_PS(m0,m1,m2,m3);_mm_store_ps(dest+h*i+j,m0);_mm_store_ps(dest+h*(i+1)+j,m1);_mm_store_ps(dest+h*(i+2)+j,m2);_mm_store_ps(dest+h*(i+3)+j,m3);}

}}

void transpose(float* src, float* dest, int w, int h)

{//naive imprimentationfor(int j=0;j<h;j++){

for(int i=0;i<w;i++){dest[h*i+j] = src[w*j+i];}

}}

Page 108: マルチコアを用いた画像処理

プログラムの最適化時はメモリにも注意

•普段注目されているもの

• 積和の回数

•実は最も重要なポイント

• ロードストアの回数

• キャッシュミスの割合(プロファイリングツール)

108

Page 109: マルチコアを用いた画像処理

これまでを踏まえて本気でコードをオプティマイゼーションしたらどうなるのか?

109

Page 110: マルチコアを用いた画像処理

バイラテラルフィルタを高速化

110

void bilateralFilterNaive(Mat& src, Mat& dest, int d, double sigma_color, doublesigma_space){

Mat srcd;src.convertTo(srcd,CV_64F);Mat destd = Mat::zeros(src.size(),CV_64F);const int r = d/2;for(int j=0;j<src.rows;j++){

for(int i=0;i<src.cols;i++){double sum = 0.0;double coeff = 0.0;const double cp = srcd.at<double>(j,i);for(int l=-r;l<=r;l++){

for(int k=-r;k<=r;k++){if(sqrt(l*l+k*k)<=r && i+k>=0 && i+k<src.cols && j+l>=0 && j+l<src.rows ){double c = -0.5*exp(((srcd.at<double>(j+l,i+k)-cp)*(srcd.at<double>(j+l,i+k)-cp))/(sigma_color*sigma_color));double s = -0.5*exp((l*l+k*k)/(sigma_space*sigma_space));coeff+=c*s;sum+=srcd.at<double>(j+l,i+k)*c*s;}}}

destd.at<double>(j,i)=sum/coeff;}

}destd.convertTo(dest,src.type());

}

22.6秒

バイラテラルフィルタエッジ保持する平滑化フィルタ※パラメータ:半径19画素

※学生が書いたコードです.

Page 111: マルチコアを用いた画像処理

バイラテラルフィルタを高速化

111

class BilateralFilter_8u_InvokerSSE4 : public cv::ParallelLoopBody{public:BilateralFilter_8u_InvokerSSE4(Mat& _dest, const Mat& _temp, int _radiusH, int _radiusV, int _maxk,int* _space_ofs, float *_space_weight, float *_color_weight) :temp(&_temp), dest(&_dest), radiusH(_radiusH), radiusV(_radiusV),maxk(_maxk), space_ofs(_space_ofs), space_weight(_space_weight), color_weight(_color_weight){}

virtual void operator() (const Range& range) const{int i, j, k;int cn = dest->channels();Size size = dest->size();

#if CV_SSE4_1bool haveSSE4 = checkHardwareSupport(CV_CPU_SSE4_1);#endifif( cn == 1 ){uchar CV_DECL_ALIGNED(16) buf[16];

uchar* sptr = (uchar*)temp->ptr(range.start+radiusV) + 16 * (radiusH/16 + 1);uchar* dptr = dest->ptr(range.start);

const int sstep = temp->cols;const int dstep = dest->cols;for(i = range.start; i != range.end; i++,dptr+=dstep,sptr+=sstep ){j=0;#if CV_SSE4_1if( haveSSE4 ){for(; j < size.width; j+=16)//16 pixel unit{int* ofs = &space_ofs[0];

float* spw = space_weight;

const uchar* sptrj = sptr+j;

const __m128i sval0 = _mm_load_si128((__m128i*)(sptrj));

__m128 wval1 = _mm_set1_ps(0.0f);__m128 tval1 = _mm_set1_ps(0.0f);__m128 wval2 = _mm_set1_ps(0.0f);__m128 tval2 = _mm_set1_ps(0.0f);__m128 wval3 = _mm_set1_ps(0.0f);__m128 tval3 = _mm_set1_ps(0.0f);__m128 wval4 = _mm_set1_ps(0.0f);__m128 tval4 = _mm_set1_ps(0.0f);

const __m128i zero = _mm_setzero_si128();for(k = 0; k < maxk; k ++, ofs++,spw++){__m128i sref = _mm_loadu_si128((__m128i*)(sptrj+*ofs));_mm_store_si128((__m128i*)buf,_mm_add_epi8(_mm_subs_epu8(sval0,sref),_mm_subs_epu8(sref,sval0)));

__m128i m1 = _mm_unpacklo_epi8(sref,zero);__m128i m2 = _mm_unpackhi_epi16(m1,zero);m1 = _mm_unpacklo_epi16(m1,zero);

const __m128 _sw = _mm_set1_ps(*spw);

__m128 _w = _mm_mul_ps(_sw,_mm_set_ps(color_weight[buf[3]],color_weight[buf[2]],color_weight[buf[1]],color_weight[buf[0]]));__m128 _valF = _mm_cvtepi32_ps(m1);_valF = _mm_mul_ps(_w, _valF);tval1 = _mm_add_ps(tval1,_valF);wval1 = _mm_add_ps(wval1,_w);

_w = _mm_mul_ps(_sw,_mm_set_ps(color_weight[buf[7]],color_weight[buf[6]],color_weight[buf[5]],color_weight[buf[4]]));_valF =_mm_cvtepi32_ps(m2);_valF = _mm_mul_ps(_w, _valF);tval2 = _mm_add_ps(tval2,_valF);wval2 = _mm_add_ps(wval2,_w);

m1 = _mm_unpackhi_epi8(sref,zero);m2 = _mm_unpackhi_epi16(m1,zero);m1 = _mm_unpacklo_epi16(m1,zero);

_w = _mm_mul_ps(_sw,_mm_set_ps(color_weight[buf[11]],color_weight[buf[10]],color_weight[buf[9]],color_weight[buf[8]]));_valF =_mm_cvtepi32_ps(m1);_valF = _mm_mul_ps(_w, _valF);wval3 = _mm_add_ps(wval3,_w);tval3 = _mm_add_ps(tval3,_valF);

_w = _mm_mul_ps(_sw,_mm_set_ps(color_weight[buf[15]],color_weight[buf[14]],color_weight[buf[13]],color_weight[buf[12]]));_valF =_mm_cvtepi32_ps(m2);_valF = _mm_mul_ps(_w, _valF);wval4 = _mm_add_ps(wval4,_w);tval4 = _mm_add_ps(tval4,_valF);}tval1 = _mm_div_ps(tval1,wval1);tval2 = _mm_div_ps(tval2,wval2);tval3 = _mm_div_ps(tval3,wval3);tval4 = _mm_div_ps(tval4,wval4);_mm_stream_si128((__m128i*)(dptr+j), _mm_packus_epi16(_mm_packs_epi32( _mm_cvtps_epi32(tval1), _mm_cvtps_epi32(tval2)) , _mm_packs_epi32( _mm_cvtps_epi32(tval3), _mm_cvtps_epi32(tval4))));}}#endiffor(; j < size.width; j++){const uchar val0 = sptr[0];float sum=0.0f;float wsum=0.0f;for(k=0 ; k < maxk; k++ ){int val = sptr[j + space_ofs[k]];float w = space_weight[k]*color_weight[std::abs(val - val0)];sum += val*w;wsum += w;}//overflow is not possible here => there is no need to use CV_CAST_8Udptr[j] = (uchar)cvRound(sum/wsum);}}}else{short CV_DECL_ALIGNED(16) buf[16];

const int sstep = 3*temp->cols;const int dstep = dest->cols*3;

uchar* sptrr = (uchar*)temp->ptr(3*radiusV+3*range.start ) + 16 * (radiusH/16 + 1);uchar* sptrg = (uchar*)temp->ptr(3*radiusV+3*range.start+1) + 16 * (radiusH/16 + 1);uchar* sptrb = (uchar*)temp->ptr(3*radiusV+3*range.start+2) + 16 * (radiusH/16 + 1);

uchar* dptr = dest->ptr(range.start);

for(i = range.start; i != range.end; i++,sptrr+=sstep,sptrg+=sstep,sptrb+=sstep,dptr+=dstep ){j=0;#if CV_SSE4_1if( haveSSE4 ){for(; j < size.width; j+=16)//16 pixel unit{int* ofs = &space_ofs[0];

float* spw = space_weight;

const uchar* sptrrj = sptrr+j;const uchar* sptrgj = sptrg+j;const uchar* sptrbj = sptrb+j;

680倍速

33.2ミリ秒

• 同じCPU

• 同じコンパイラオプション• 精度そのまま,近時なし

Page 112: マルチコアを用いた画像処理

研究紹介

112

Page 113: マルチコアを用いた画像処理

MRF,CRFを並列化

無向グラフは高い並列性

確率伝播法など(BP,SGM,ICP)• ボクセル空間でのフィルタ• 画素(x,y)尤度(z)の次元処理• 尤度を連続になるようにするとベクトル化しやすい• Middleburyのライブラリなど離散的なものは特に

グラフ処理• グラフカット

• Cuda Cut:グラフカットのGPU実装• SIMDによるグラフ表現は少し難しい

• SIMDの拘束は,ノード間のグラフ定義の自由度を著しく低下させる

113

Page 114: マルチコアを用いた画像処理

超解像を並列処理

超解像は基本的にはFIRフィルタの繰り返しで表現可能

•全工程が並列処理可能• デノイズ

• デブラー

• アップサンプル

• 位置あわせ

114

Page 115: マルチコアを用いた画像処理

機械学習を並列処理

数万枚もの画像処理が必要→複数台の計算機で並列処理!

• Mahout https://mahout.apache.org/• Hadoop上で動く機械学習ライブラリ• Map Reduce• スケーラビリティを最重視

• Jubatus http://jubat.us/ja/• 日本発!!!!!• データ分析に特化• 複雑な処理も可能• 低レイテンシ

115

Page 116: マルチコアを用いた画像処理

エッジ保持平滑化フィルタ

116

• セグメンテーション• スーパーピクセル• ステレオ対応• オプティカルフロー推定• アルファマッティング• トランジションマップ• 顕著性マップ• トーンマッピング

• etc…

バイラテラルフィルタ:エッジ保持しつつ,平滑化• 用途はデノイジングだけではない!• ラベリング問題全般に使用可能!!

Page 117: マルチコアを用いた画像処理

アルゴリズムの実装と並列化

•グローバル最適化(MRF,CRF,凸最適化...)よりも

•フィルタ処理(bilateral filter, guided filter…)のほうが

圧倒的に高速く,並列性も高い

解くべき問題に必要とされる精度が,フィルタリングで十分ならばフィルタリングを選択すべき

117

Page 118: マルチコアを用いた画像処理

コンピュテーショナルフォトグラフィ

•エッジ保持平滑化フィルタ• アルゴリズム的に高速

•イメージベーストレンダリング• 3次元画像

• ノンフォトリアリスティックレンダリング

118

Page 119: マルチコアを用いた画像処理

OpenCP: library for computational photographyhttps://github.com/norishigefukushima/OpenCP

Filters

• Fast Gaussian IIR filter

• *bilateral filter and its fast implementations or variants: *separable filter *bilateral grid

*constant time O(1) bilateral filter *real-time O(1) bilateral filter *joint bilateral filter *trilateral filter *dual bilateral filter *weighted (joint) bilateral filter epsilon filter

• cost volume filters, histogram filters:*3D bilateral filter *3D guided filter *weighted mode filter *constant time median filter *joint nearest filter

• Other edge preserving filters :non-local means filter guided filter domain transform filter recursive bilateral filter L0 Smoothing Weighted least squre (WLS) smoothing Gaussian KD-Tree permutohedral lattice adaptive manifold shiftable DXT thresholding filter

Various applications• De-noising

• De-blurirng

• up-sample/single image super resolution

• flash/non flash photography

• HDR

• colorization

• detail enhancement

• stylization, abstraction

• pencil sketch

• up-sample for pixel art, depth map

• removing coding noise

• blur regeneration

• Haze remove

• depth map estimation/refinement

• optical flow estimation/refinement

• alpha matting

119

Page 120: マルチコアを用いた画像処理

Removing Depth Map Coding Distortion by Using Post Filter Set

•デプスマップの符号化歪みをリアルタイム除去するアルゴリズム

•10数ms以内にすべての処理を終了させるビデオプロセッシング

120

Proc. IEEE International Conference on Multimedia and Expo (ICME 2013), July 2013.

projectページ:

http://nma.web.nitech.ac.jp/fukushima/research/depthmap_postfilter.html

Page 121: マルチコアを用いた画像処理

Weighted Joint Bilateral Filter with Slope Depth Compensation Filter for Depth Map Refinement

121

Proc. International Conference on Computer Vision Theory and Applications (VISAPP 2013), Feb. 2013.

実時間でデプスマップの輪郭補正を行う手法フィルタ処理をすべてTBB, SIMDで並列化

Kinect等のインプットがかなりきれいに

projectページ:

http://nma.web.nitech.ac.jp/fukushima/research/weightedj

ointbilateralfilter.html

Page 122: マルチコアを用いた画像処理

Filter Based Alpha Matting for Depth Image Based Rendering

•左右の画像から任意視点の画像を合成

•物体境界をアルファマッティングすることで高品質な画像合成を実現

•マッティング処理を並列化

122

in Proc. IEEE Visual Communications and Image Processing (VCIP), Nov. 2013

projectページ:

http://nma.web.nitech.ac.jp/fukushima/research/viewsynth

esis.html

Page 123: マルチコアを用いた画像処理

アルゴリズムの最適な選択

並列性,メモリ効率を考えると最適なアルゴリズムは通常とは異なる

• クイックソートが最適とは限らない→バイトニックソート• O(1)フィルタは小さいカーネルのときは重たい

インテグラルイメージ vs ナイーブな実装

O(1)メディアンフィルタ vs 3x3メディアンフィルタ

並列化を考えると,かなりの数のアルゴリズムがナイーブな実装がより実用的

123

Page 124: マルチコアを用いた画像処理

まとめ

• CPU上での並列化プログラミング• ムーアの法則,アムダールの法則,粒度,SIMD, MIMD

• デザインパターン: Map, Stencil, Reduction, Scan, Fork-join, Pile-line

• OpenMP, SIMD Intrinsics

• メモリIO

124

共同研究先募集中「その処理,680倍高速化します!」

※当社比調べ

本日のコードはGithub上にアップロード

https://github.com/norishigefukushima/SSII2014

Page 125: マルチコアを用いた画像処理

参考文献

125

Page 126: マルチコアを用いた画像処理

The Art of Multiprocessor Programming

• OSに近い場所の話から始めて並列化の事例まで説明した教科書

126

The Art of Multiprocessor Programming 並行プログラミングの原理から実践まで [大型本]

Maurice Herlihy (著), Nir Shavit (著), 株式会社クイープ (翻訳)

Page 127: マルチコアを用いた画像処理

コンピュータの構成と設計

•計算機アーキテクチャから学習する

•一般的な教科書:学部生の教科書

127

Page 128: マルチコアを用いた画像処理

アセンブラ画像処理プログラミング―SIMDによる処理の高速化

128

SIMDによる画像処理について書かれたほぼ唯一の本

すべてアセンブラなため入門者向けではない基本的な事項を列挙発展的な内容の記述は少ない

Page 129: マルチコアを用いた画像処理

Slideshare

組み込み関数(intrinsic)によるSIMD入門http://www.slideshare.net/FukushimaNorishige/simd-10548373

• intrinsicsを使ったSIMDプログラミング資料

• 日本語はこれだけ!?

129

Page 130: マルチコアを用いた画像処理

Intelの日本語技術資料

• Webページ• http://www.intel.co.jp/content/www/jp/ja/developer/download.html

•インテル® 64 アーキテクチャーおよびIA-32 アーキテクチャー最適化リファレンス・マニュアル(なんと788ページ)• ソフトウェア開発者向けコード最適化マニュアル

130

Page 131: マルチコアを用いた画像処理

OpenCV, Eigen, fftw, x264,ffmpegのソースコード• SIMDや並列化の参考になるコードがたくさん

• OpenCV http://opencv.org/

• Eigen http://eigen.tuxfamily.org/index.php?title=Main_Page

• fftw http://www.fftw.org/

• x264 http://www.videolan.org/developers/x264.html

• ffmpeg http://www.ffmpeg.org/

• libjpeg-turbo http://libjpeg-turbo.virtualgl.org/

• WebP https://developers.google.com/speed/webp/?csw=1

131

Page 132: マルチコアを用いた画像処理

その他の話題

132

Page 133: マルチコアを用いた画像処理

高速化の手順

1. コンパイラを変える

2. 最適化が本当に必要か一歩立ち止まる

3. 最良なアルゴリズムの適用する

4. 演算数を減らす1. 代入,LUTの使用

2. ループの外へ計算結果を

5. メモリアクセスをシーケンシャルにする

6. マルチコア並列化を行う

7. SIMD最適化を行う

133

Page 134: マルチコアを用いた画像処理

1日で覚える簡単高速化

•コンパイラをiccに

•最も速度に影響を与えるのはメモリの配置• データはアクセスする連続に並べる

• メモリ使用量を減らす(テンポラリを極力減らす)

•マルチコアの利用~OpenMPによる並列化~• ループの外側に #pragma omp parallel for

134

Page 135: マルチコアを用いた画像処理

OpenMP遅いとおもったら?

•同一メモリにアクセスしていませんか?メモリアクセスの衝突が生じ,バスの取り合いが起こっている可能性があります.共有メモリといってもキャッシュは各CPUが持っているためその構造を生かして衝突を回避しましょう.

• Parallel 指示行でスレッド生成しています.このスレッド生成は少ないほうがオーバーヘッドが少なくて済みます.すべてのループに#pragma omp parallel forを記述するのではなく外側に並列化構造を作り#pragma omp forで済むか確認しましょう.

• Intel TBBを試してみましょう.

Page 136: マルチコアを用いた画像処理

MPI (Message Passing Interface)について

•複数の計算機のプロセッサをマルチコアとみなして計算する言語,ライブラリ

•メモリの状態を共有するために,メッセージをやり取りし,メモリの状態の同期が必要(コスト大)

•画像処理の場合,そこまでする( MPIまで使う)なら複数台のマシンに別の画像を投げたほうがパフォーマンスが高い

136

Page 137: マルチコアを用いた画像処理

アルゴリズムが並列化可能かの簡易テスト

•順番どおりに動かないと計算結果がおかしくなる• 逆順に動作させても動くかどうかを検証

137

Page 138: マルチコアを用いた画像処理

用語:並行性 vs 並列性

•並行性(コンカレンシー)とは• 複数のタスクを同時に実行・処理する性質

•並列性(パラレリズム)とは• 並行性を活用して,問題を最短時間で解こうとすること

※いろいろ定義の流派がありますがこちらで解釈しています.

138

Page 139: マルチコアを用いた画像処理

コンパイラの自動並列化はどこまで出来るのか?•簡単な構造なら自動並列化してくれるが...

• gcc- O3 ...のような手軽さで高速化はまだまだ先

• Intel SPMD Program Compiler• オープンソースのSIMD用コンパイラ

• http://ispc.github.io/

• BSDライセンス

139

Page 140: マルチコアを用いた画像処理

高速化されたライブラリの情報

無料

• OpenCV• 画像処理,行列演算,FFT,GPU関数も

• Eigen行列演算

• fftw• 無償最速 fftライブラリ

• ffte• 最近のfftライブラリ.並列化した場合こちらのほうが速いときも

有料

• Intel® Integrated Performance Primitives (IPP)• CPU最速信号処理,画像処理

• Intel® Math Kernel Library (MKL)• BLAS,LAPACK,FFTなど

140

Page 141: マルチコアを用いた画像処理

Cuda vs OpenCL

• よほどコアな処理(nvidiaのカードに特化した処理など)をしない限りCudaとOpenCLは処理速度は変わらない• だたし,Cudaのほうが短く書くことが出来る

• OpenCLはCPU(マルチコア)向けにもコードを書くことが出来るため汎用性が高い• ただしOpenCLでかかれたマルチコア用のコードはOpenMPで並列化するよりも遅い

• Cudaはnvidia専用言語しかしデファクトスタンダートに

• 汎用性を考えるとOpenCLが有利?

141

Page 142: マルチコアを用いた画像処理

画像処理のSIMDベクトル化に関する論文

• R. Kutil, “Parallelization of IIR filters using SIMD extensions,”Proc. IWSSIP, pp. 65-68, Bratislava, June 2008.

• Shahbahrami, A.; Juurlink, B.; Vassiliadis, S., "Performance comparison of SIMD implementations of the discrete wavelet transform," Application-Specific Systems, Architecture Processors, 2005. ASAP 2005. 16th IEEE International Conference on , vol., no., pp.393,398, 23-25 July 2005

142