maxwell と java cudaプログラミング

75
森野慎也, シニアCUDAエンジニア、エヌビディアジャパン Maxwell Java CUDA プログラミング

Upload: nvidia-japan

Post on 06-Jan-2017

2.824 views

Category:

Software


0 download

TRANSCRIPT

森野慎也, シニアCUDAエンジニア、エヌビディアジャパン

Maxwell と Java CUDA プログラミング

2

本資料について

本資料は、2016/1/27に実施された

「MaxwellとJava、C#のためのCUDA」の講演資料です。

本セミナーの詳細については、Compassのセミナーページをご参照ください。

http://nvidia.connpass.com/event/24764/

サンプルソースコードは、githubより、ご取得ください。

https://github.com/shin-morino/JCudaExamples

ご意見、ご質問などは、以下にお寄せください。

[email protected]

3

Agenda

1. Maxwellの紹介 (20 min)

2. Java CUDAプログラミング

基本的なプログラミング (30 min)

例題1 : 文字列検索 (15 min)

例題2 : ヒストグラム (15 min)

4

Quadro M6000 / GeForce GTX TITAN X

Compute Capability 5.2, 3072 CUDA Cores, 300 ~ GB/sec memory bandwidth

5

TESLA M40World’s Fastest Accelerator

for Deep Learning0 1 2 3 4 5 6 7 8 9

Tesla M40

CPU

8x FasterCaffe Performance

# of Days

Caffe Benchmark: AlexNet training throughput based on 20 iterations, CPU: E5-2697v2 @ 2.70GHz. 64GB System Memory, CentOS 6.2

CUDA Cores 3072

Peak SP 7 TFLOPS

GDDR5 Memory 12 GB

Bandwidth 288 GB/s

Power 250W

Reduce Training Time from 8 Days to 1 Day

6

TESLA M4Highest Throughput

Hyperscale Workload Acceleration

CUDA Cores 1024

Peak SP 2.2 TFLOPS

GDDR5 Memory 4 GB

Bandwidth 88 GB/s

Form Factor PCIe Low Profile

Power 50 – 75 W

Video Processing

4x

Image Processing

5x

Video Transcode

2x

Machine Learning Inference

2x

H.264 & H.265, SD & HD

Stabilization and Enhancements

Resize, Filter, Search, Auto-Enhance

Preliminary specifications. Subject to change.

7

SMM: Maxwell ストリーム・マルチプロセッサ

Kepler : SMX Maxwell : SMM

SMM: Maxwell ストリーム・マルチプロセッサ

より効率重視のアーキテクチャ

Kepler(SMX)との違い

命令スケジューリングの改善 (128 CUDA core, 4モジュール構成)

命令latencyの短縮

最大スレッドブロック数増

データパスの変更

共有メモリサイズ増

共有メモリAtomicsの高速化

Kepler(SMX) Maxwell(SMM)

CONTROL CONTROL

CONTROL

CONTROL

CONTROL

192 cores

32 32

32 32

9

レイテンシの短縮

間を埋めるために複数のWarpが必要

Warp X

Warp A

Warp B

Warp C

Warp …

Warp …

Warp X

Warp A

Warp B

typ.

11 c

lock

間を埋めるには、より少ないWarp数で足りる。

より低い並列度で性能が出やすくなる。

Warp X

Warp A

Warp B

Warp …

Warp …

Warp A

Warp B

レイテンシが短縮された

Kepler Maxwell

10

Keplerのデータパス

Regis

ter

File

(変数

)

演算

CU

DA C

ore

s

PCIe

高速 (数TB / sec) 低速 (~ 10 GB/sec)

Shared Mem

Tex

Cache

Texture

Global RO

Global

Local

L1

Cache

SM

L2 C

ache

Glo

bal M

em

ory

(GPU

DRAM

)

Host

(PC)

DRAM

11

MaxwellのデータパスUnified L1/Tex Cacheの導入

SM

Regis

ter

File

(変数

)

L2 C

ache

演算

CU

DA C

ore

s

Glo

bal M

em

ory

(GPU

DRAM

)

Host

(PC)

DRAM

PCIe

高速 (数TB / sec) 低速 (~ 10 GB/sec)L1/Te

xCache

Texture

Shared Mem

Global

Local

Global RO

12

Maxwellでの機能強化

- シェアードメモリ

- 容量が増加。

- これまで : 最大 48 KB, Maxwell : 96 KB(CC5.2), 64 KB(CC5.0, 5.3)

- アトミクスが速くなった。

GPU Pro Tip: Fast Histograms Using Shared Atomics on Maxwellhttp://devblogs.nvidia.com/parallelforall/gpu-pro-tip-fast-histograms-using-shared-atomics-maxwell/

- SM上で動作する最大ブロック数が 32(Cc3.x) -> 64 (Cc5.x)になった。

- 64 thread / block でも、並列度が確保できるようになった。

Compute Capability 5.x

スペック比較

Kepler

Tesla K40

Maxwell

Quadro M6000

CUDAコア 2,880 3,072

SM数 15 24

最大スレッドブロック数(/SM) 16 32

シェアードメモリ(/SM) 48 KB 96 KB

L2サイズ 1.5 MB 3 MB

TFLOPS(単精度) 4.3 6.1

TFLOPS(倍精度) 1.43 0.19

メモリ帯域 288 GB/s 317 GB/s

TDP 235 W 250 W

14

Java CUDAプログラミング

15

JavaでCUDAプログラミング

- JCuda (http://www.jcuda.org/)

- CUDAのJavaバインディング, オープンソース

- CUDA Runtime API, Driver APIを使用することができる。

- Rootbeer (https://github.com/pcpratts/rootbeer1)

- オープンソース

- Javaでカーネルを実装できる。

- CUDA4J (http://www-01.ibm.com/support/knowledgecenter/SSYKE2_7.0.0/com.ibm.java.lnx.71.doc/user/gpu_developing.html?lang=ja)

- IBMからのリリース

- PowerPC + NVIDIA GPUで動作

16

JCudaの構成

- JavaからCUDA APIを呼び出す

- C/C++向け CUDA APIを踏襲

- Java言語仕様より差異はある。( 例えば、ポインタの扱い。 )

- CUDA C/C++上のプログラミングをJavaに読み替えて実装・実行。

http://www.jcuda.org/

CUDA Driver API

CUDA Runtime

Driver

JNI

JCuda Driver APIJCuda Runtime API

JNI

Java Application

17

JCuda + JavaでCUDAプログラミング

- 環境 : CUDA 7.5, Eclipse 4.5.1, Java8, JCuda 0.7.5

- フリーで使える環境を使います。

- Javaと、CUDAデバイスコードのみで、プログラミングします。

- CPUコードで、C/C++を使いません。

- Visual Studioは、「カーネルデバッグ」のみで使います。

- Community Editionが、用途に応じ、無償で利用可能

18

JCuda サンプル1 : 配列の和

19

プログラム例

- 配列に収められたa[i], b[i] を、並列に加算し、c[i] を得る

配列の和

c[i] = a[i] + b[i]

20

配列の和:メモリの扱い

ホスト(CPU) デバイス(GPU)

カーネルdc[i] = da[i] + db[i]

*a, *bに値を設定

ホスト->デバイス転送 a-> da, b->db

ホスト <- デバイス転送 c <- dc

float *da, *db, *dc をアロケート (デバイスメモリ)

結果表示・検証

float *da, *db, *dc を開放 (デバイスメモリ)

float *a, *b, *c を開放

カーネル実行依頼

float *a, *b, *c をアロケート

21

並列化(カーネル設計)

- 1 スレッドで、一つの要素の和を計算。

- 複数のブロックに分割。1 Blockあたりの最大スレッド数は、1024。 (図はBlockあたり4スレッドとした)

a[i]

b[i]

c[i]

Block[0]

0 1 2 3

+ + + +

15 14 13 12

Block[1]

4 5 6 7

+ + + +

11 10 9 8

Block[2]

8 9 10 11

+ + + +

7 6 5 4

Block[3]

12 13 14 15

+ + + +

3 2 1 0

Thread ID ? 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

22

Global ID

- Global ID : Grid内で一意

- blockDim.x * blockIdx.x + threadIdx.x

- threadIdx.x

- Thread番号、Block内で一意

- blockIdx.x

- Block番号、Grid内で一意

- blockDim.x

- Block内のスレッド数

GPUスレッドの通し番号

0

1

2

3

Global ID

4

5

6

7

threadIdx

0 Thread

1 Thread

2 Thread

3 Thread

0

blockIdx

threadIdx

0 Thread

1 Thread

2 Thread

3 Thread

1

blockIdx

23

カーネル実装

__global__

void addArrayKernel(float *dc, const float *da, const float *db, int size) {

/* Global IDを算出 */

int globalID = blockDim.x * blockIdx.x + threadIdx.x;

if (globalID < size) { /* 範囲チェック */

/* 自スレッド担当の要素のみ、処理 */

dc[globalID] = da[globalID] + db[globalID];

}

}

24

配列の和 : ホストコード

int main() {static const int size= 256 * 100;int memSize = sizeof(float) * size;float *a, *b, *c, *da, *db, *dc; /* ホストもデバイスもメモリは同じポインタ型 *//* ホスト側メモリの確保と値の初期化(略)*//* GPU側メモリをアロケート */cudaMalloc(&da, memSize); cudaMalloc(&db,memSize); cudaMalloc(&dc, memSize);cudaMemcpy(da, a, memSize, cudaMemcpyHostToDevice); /* メモリ転送(Host→Device) */cudaMemcpy(db, b, memSize, cudaMemcpyHostToDevice);

int blockDim = 256; int gridDim = (size + blockDim – 1) / blockDim;addArrayKernel<<<gridDim, blockDim>>>(dc, da, db, size);

cudaMemcpy(c, dc, memSize, cudaMemcpyDeviceToHost); /* メモリ転送(Host←Device) *//* 表示などの処理 (略) */cudaFree(da); cudaFree(db); cudaFree(dc);free(a); free(b); free(c);cudaDeviceReset();

}

CUDA C/C++ のソースコード

25

まず動かしてみる

サンプル中の、vectoradd_runtime.JCudaVectorAdd.javaを実行します。

配列の和 : JCudaバージョン

26

JCuda でのプログラミングCUDA C/C++との違い

CUDA初期化

モジュールのロード

カーネル関数ポインタの取得

CUDA終了処理

アプリケーション

CUDA Runtime APIでは、暗黙的に実行される。

CUDA Runtime APIでは、暗黙的に実行される。

CUDA Runtime API、CUDA Driver APIを使用してCUDA C/C++と同等のプログラミングが行える

カーネルコンパイル PTXの作成

28

1. カーネルコードからPTXを準備する

方法1:

nvcc を用いて、カーネルソースをコンパイル。PTXを生成する。

$ nvcc -ptx -arch compute_50 source.cu -o source.ptx

• -archの引数は、compute_xx.xxは、GPUのCompute Capabilityに合わせる。

• Windowsの場合には、nvcc.exeだけでなく、cl.exeにパスを通しておく。(vcvars64.bat)

方法2:

NVRTCを用いて、ランタイムコンパイルを行う。(CUDA 7.5 ではPreview Release)

• Cインターフェースで提供されるライブラリ

• JNAを使用したクラスを準備しました。(utils.RuntimeCompiler)

JNA : Java Native Accesshttps://github.com/java-native-access/jna

(今日は、こちらを使います。)

CUDA C/C++ Runtime API との違い

29

デモ

方法1:

NVCCがパスが通っていること。Windowsでは、cl.exeにもパスが通っていること。

$ nvcc -ptx JCudaVectorAddKernel.cu -arch compute_50 -o JCudaVectorAddKernel.cu

方法2:

tests.RuntimerCompilerTestMain.javaを実行

ソースコードからPTXの生成

30

2. CUDAの初期化は明示的に行う。

/* CUDAの環境を初期化 */

cuInit(0);

/* デバイス設定 */

CUdevice device = new CUdevice();

cuDeviceGet(device, 0);

/* コンテクスト作成 */

CUcontext context = new CUcontext();

cuCtxCreate(context, 0, device);

CUDA C/C++ Runtime API との違い

CUDA Driver APIを用いて初期化する。

- 初期化(cuInit(0))

- デバイス設定

- CUDAコンテクストの作成

CUDA Runtime APIを使用している場合には、自動的に実行される。

31

3. CUDAのモジュール、カーネルは、自前でロードする。

/* 作成したPTXは、ptxに収められている */

byte[] ptx = …;

/* モジュールの作成とロード */

CUmodule =new CUmodule();

cuModuleLoadData(module, ptx);

/* カーネル関数へのポインタを取得 */

CUfunction function = new CUfunction();

cuModuleGetFunction(function, module, "add");

CUDA C/C++ Runtime API との違い

CUDA Driver APIを用いる。

- Moduleの作成 (PTXのロード)

- カーネル関数へのポインタを取得

*CUDA Runtime APIを使用している場合には、自動的に実行される。

32

3. CUDAのモジュール、カーネルは、自前でロードする。

extern “C” /* 関数シンボルを ”add” とする。 */

__global__

void add(float *dc, const float *da, const float *db, int size) {

/* Global IDを算出 */

int globalID = blockDim.x * blockIdx.x + threadIdx.x;

if (globalID < size) { /* 範囲チェック */

/* 自スレッド担当の要素のみ、処理 */

dc[globalID] = da[globalID] + db[globalID];

}

}

カーネルソース

33

4.ネイティブポインタは、Pointerクラスで扱う。

/* ホスト(CPU)側の配列 */byte[] inputA =new byte[numElements];(… 略 …)

/* デバイスメモリをアロケート */Pointer dInputA = new Pointer();int bufSize = Sizeof.FLOAT * numElements;cudaMalloc(dInputA, bufSize);

/* デバイスメモリdInputAに、ホストメモリ inputAを転送 */cudaMemcpy(dInputA, Pointer.to(inputA), bufSize, cudaMemcpyHostToDevice);

CUDA C/C++ Runtime API との違い

jcuda.Pointer クラス

ネイティブポインタを保持する。

ネイティブポインタへの参照としても利用される。

jcuda.Pointer.to()メソッド

ホスト側オブジェクトのネイティブポインタを

Pointerクラスのインスタンスとして取得

34

5. カーネルは、Driver APIを使用してローンチする

int blockDim = 128;int gridDim = (numElements + blockDim - 1) / blockDim;

Pointer kernelParameters = Pointer.to(Pointer.to(new int[]{numElements}),Pointer.to(dInputA), Pointer.to(dInputB),Pointer.to(dOutput)

);

cuLaunchKernel(addFunction, gridDim, 1, 1, // GridDimblockDim, 1, 1, // BlockDim0, null, // シェアードメモリのサイズ、ストリームkernelParameters, null // カーネル引数

);

CUDA C/C++ Runtime API との違い

カーネル引数

Pointerの配列へのPointerで定義する。

カーネルローンチ

cuLaunchKernel()関数を用いる。

サンプルコードは、add<<<gridDim, blockDim>>>(nElements, dC, dA, dB)に対応

35

5. 終了処理はアプリケーションから行う

/* メモリ解放 */cudaFree(dInputA);cudaFree(dInputB);cudaFree(dOutput);

cudaDeviceReset();

/* モジュールアンロード */cuModuleUnload(module);/* コンテクストの破棄 */cuCtxDestroy(context);

CUDA C/C++ Runtime API との違い

- メモリ解放

デバイスメモリは、ガベージコレクトされない。

- 終了処理

モジュールのアンロード

コンテクストの破棄

36

ソースレビュー

vectoradd_runtime.JCudaVectorAdd.javaを参照

サンプルコードは、githubから取得願います。

https://github.com/shin-morino/JCudaExamples

37

デバッグ

- カーネル中では、以下の機能が使える

- printf()

- assert()

- CUDAのメモリチェッカ (cuda-memcheck)

- アクセス範囲チェック、未初期化値のチェックなど

- Windowsでは、Nsight Visual Studio Editionが動作 (参考まで)

38

ブレークポイントの挿入

extern “C” /* 関数シンボルを ”add” とする。 */

__global__

void add(float *dc, const float *da, const float *db, int size) {

/* Global IDを算出 */

int globalID = blockDim.x * blockIdx.x + threadIdx.x;

if (globalID < size) { /* 範囲チェック */

/* 自スレッド担当の要素のみ、処理 */

asm volatile (“brkpt;”); /* ブレークポイントを挿入 */

dc[globalID] = da[globalID] + db[globalID];

}

}

デバッグ事前準備

39

事前準備 : Windows

環境変数NSIGHT_CUDA_DEBUGGER=1

Nsight Monitorを、管理者権限で起動しておく。

デバッグ

40

プログラムを実行させると…

- ポップアップが現れる - デバッガのアタッチを待つ

デバッグ

41

Visual Studio側の操作

- 「プロセスにアタッチ…」を選択

- 以下を指定

トランスポート : Nsight GPU Debugger

修飾子 : localhost

- プロセスを選択

デバッグ

42

Visual Studio側の操作

- ブレークポイントで停止

デバッグ

43

JavaでCUDAを使う

- 少々の工夫で、Javaでも、CUDAアプリケーションを実装できる。

- JCudaを用いて、CUDA Driver/Runtime APIを利用できる。

- JCudaを用いて、呼び出し。

- C/C++の場合、自動で行われる処理(初期化など)は、アプリケーション中に実装する必要あり。

- カーネルのデバッグも、ツールを使用して行う。

中間まとめ

44

例題1 : 文字列検索

45

文字列検索

- 大きな文字列から、一致するパターンを検索する。

- CPU・GPUで、検索速度を比較

- 文字列データ : 500 MBの円周率(テキスト)

応用例 1

46

Boyer-Moore法

- 検索をスキップすることで高速化。

- 検索パターンの末尾から、文字ごとの比較を行う。

- 一致しない文字から、スキップする距離を取得

CPUにおける文字列検索

47

Boyer-Moore法検索時の動作 (1)

3 . 1 4 1 5 9 2 6 5 3

2 6 5

(1)末尾の”1” と “5” を比較。不一致。

(2) “1”は、”265”の中に存在しない。“1”がパターンと重ならないようにしたい。

(3) 3つスキップ。

検索位置

48

Boyer-Moore法検索時の動作 (2)

3 . 1 4 1 5 9 2 6 5 3 …

(1)末尾の”5” と “5” を比較。一致。

(3) 1は、”265”の中に存在しない。“1”が存在する範囲では、一致することはない。2つスキップ。

2 6 5

(2) ”1” と “6” を比較。不一致。

検索位置

49

Boyer-Moore法検索時の動作 (3)

3 . 1 4 1 5 9 2 6 5 3 …

(1)末尾の”2” と “5” を比較。不一致。

(2) 文字列中の“2”は、検索パターン”265”の中に存在する。2つスキップすると、”2”は一致する。

2 6 5

検索位置

50

Boyer-Moore法検索時の動作 (4)

3 . 1 4 1 5 9 2 6 5 3 …

(1) “653”と”653”を末尾から比較。一致。

2 6 5

検索位置

51

文字列検索

- 検索をスキップすることで高速化。

- 検索パターンの末尾から、文字ごとの比較を行う。

- 一致しない文字から、スキップする距離を取得

- 検索の先頭位置は、前回の検索結果に依存。

- 検索の先頭位置 = ”前回の先頭位置” + ”スキップする距離”

Boyer-Moore法

「逐次的」な処理CUDA・GPUでの並列化に向かない

52

文字列検索GPUでの並列化 : Brute-Force

3 . 1 4 1 5 9 2 6 5 3 …

Thread

文字列

文字列は 2 ?

53

文字列検索Brute-Forceでの比較を並列化

3 . 1 4 1 5 9 2 6 5 3 …

Thread

文字列

文字列は 6 ?

54

文字列検索Brute-Forceでの比較を並列化

3 . 1 4 1 5 9 2 6 5 3 …

Thread

文字列

文字列は 5 ?

スレッド 7 で、一致した

55

出力位置の確保

- 複数のスレッドから、オフセット値を書き込む場合がある。

Thread

出力値

- アトミクスを用いて、書き込み場所を決定

- atomicAdd(“一致したオフセット数”, +1);

Thread

出力値一致したオフセット数

0 1 2

0 1 2

56

カーネルソース

extern "C“ __global__void searchPatternKernel_bruteForce

(int *d_nFound, int *d_offsets, int nMaxMatched,const unsigned char *d_pattern, int patternLength,const unsigned char *d_text, int searchLength) {

/* 検索の先頭位置を算出 */int gid = blockDim.x * blockIdx.x + threadIdx.x;/* 自スレッドが検索範囲にあることを確認 */if (gid < searchLength) {

bool matched = true;/* 文字列の検索開始位置のポインタを取得 */const unsigned char *d_myPos = &d_text[gid];/* ループで一文字づつ検索 */for (int idx = 0; idx < patternLength; ++idx) {

if (d_pattern[idx] != d_myPos[idx]) {matched = false; /* 一致せず */break;

}}

/* 一致していたら */if (matched) {

/* アトミクスを用いて、出力場所を算出 */int offsetPos = atomicAdd(d_nFound, 1);/* 出力場所を取得 */if (offsetPos < nMaxMatched)

d_offsets[offsetPos] = gid;}

}}

57

評価

- CPU版については、スレッド並列版もあわせて準備。4 core, 8 thread で実行。

- 環境

- GPU : Quadro M5000FP32 : 4.3 TFLOPS (At boost clock, 1.05 GHz)メモリバンド幅 : 211 GB/sec

- CPU : Intel(R) Core(TM) i7-3820 CPU @ 3.60 GHz

- OS : Ubuntu 14.04

58

実行結果

処理 GPU 実行時間CPU(4CORE)実行時間

CPU(1CORE) 実行時間

CUDA初期化 85 ms - -

カーネルコンパイル (537 ms*) - -

デバイスメモリアロケート 8 ms - -

文字列(PI)のロード 333 ms 343 ms 364 ms

GPUへと文字列を転送 75 ms - -

検索パターン設定 1 ms - -

検索実行 42 ms 103 ms 613 ms

終了処理 43 ms - -

総計 587 ms 446 ms 977 ms

検索自体は速いもっと速くしたい

転送はGPUのみ短くしたい

* カーネルコンパイルは、事前に実行可能なため、処理時間の総計から省いています。

59

Pinned Memoryの利用

- CPUGPU 転送の高速化

- Pinnedメモリを使用する。

- GPUへとDMAでデータ転送される

- ByteBufferとしてアクセスできる

最適化

/* FileChannelの作成 */Path path = Paths.get(filename);FileChannel channel = FileChannel.open(path,

StandardOpenOption.READ);

/* ファイルサイズ(テキスト長)の取得 */long textLength = Files.size(path);

/* Pinnedメモリの確保 */piHost = new Pointer();cudaHostAlloc(piHost, textLength, cudaHostAllocPortable);

/* ByteBufferを利用して、FileChannelから読み込み */ByteBuffer bb = piHost.getByteBuffer(0, textLength);channel.read(bb);

60

4文字(バイト)まとめて比較

1 Byteごとの、文字列アクセス、比較は、効率悪い。

4 Byte、8 Byteぐらいが、効率よい。

最適化

3 . 1 4 1 5 9 2 6 5 …

Thread

文字列

2 6 5 3

61

実行結果 (最適化後)

処理GPU 実行時間

(最適化後)GPU 実行時間

CPU実行時間(4 CORE)

CPU 実行時間(1 CORE)

CUDA初期化 99 ms 85 ms - -

カーネルコンパイル (552 ms*) (537 ms*) - -

デバイスメモリアロケート 121 ms 8 ms - -

文字列(PI)のロード 74 ms 333 ms 343 ms 364 ms

GPUへと文字列を転送 45 ms 75 ms - -

検索パターン設定 < 1 ms 1 ms - -

検索実行 7 ms 42 ms 103 ms 613 ms

終了処理 93 ms 43 ms - -

総計 439 ms 587 ms 446 ms 977 ms

文字列検索

* カーネルコンパイルは、事前に実行可能なため、処理時間の総計から省いています。

62

GPUの性能を有効に使うために

- GPU上の検索は、速い。

- GPU(最適化後) : 7 ms, CPU (4 core) 103 ms

- GPUへの文字列転送 : 45 ms

- CPUには存在しない処理であり、GPUの性能が活かせない要因となる。

- GPUを有効に使うには:

データの再利用などを考慮し、「転送量を少なく」、かつ、「GPU上での処理量を多く」する。

文字列検索

63

例題2 : ヒストグラム

64

ヒストグラム

- 度数分布

- 処理

要素の値に応じて、ビンに値を足す。

- 入力データ

500万桁の円周率

度数

65

JavaにおけるCPU実装

/* ヒストグラム作成 */public static int[] make(byte[] sequence) {

/* ヒストグラム用の領域を作成 */int[] histgram = new int[256];

for (int idx = 0; idx < sequence.length; ++idx) {/* 要素に対応するビンに 1 を足す */int binIdx = sequence[idx];++histgram[binIdx];

}

return histgram;}

シングルスレッド版

66

CUDAによる並列化 : 最初のアプローチ

- スレッドごとの処理

- 1文字を取得

- 対応するビンを +1 する。

- 競合の発生

- アトミクスを用いて加算

- 速度低下の原因

ヒストグラム

3 . 1 4 …

Thread 0 1 2 3 4

5 6 7 8 9

. 0 1 3 42

Bin

67

ヒストグラムCUDAによる並列化 : 競合を減らす

Input

Bin

(1) ブロックごとに部分ヒストグラムを作成

(2) 足し合わせる 足し合わせる部分ヒストグラムの数を減らしたい。

Block0 Block1 Block2

68

ヒストグラムCUDAによる並列化 : 競合を減らす

Block0

Bin

(1)一つのスレッドで複数の入力に対して、部分ヒストグラムを作成

(2) 足し合わせる

Block1 Block2

69

MaxwellのデータパスUnified L1/Tex Cacheの導入

SM

Regis

ter

File

(変数

)

L2 C

ache

演算

CU

DA C

ore

s

Glo

bal M

em

ory

(GPU

DRAM

)

Host

(PC)

DRAM

PCIe

高速 (数TB / sec) 低速 (~ 10 GB/sec)L1/Te

xCache

Texture

Shared Mem

Global

Local

Global RO

70

ヒストグラムCUDAによる並列化 : Shared Memoryの導入

Thread

Bin

(1)一つのスレッドで複数の入力に対して、部分ヒストグラムを作成

(2) 足し合わせる

Maxwellで速くなったシェアードメモリアトミクスを使う

71

実行結果 (最適化後)

処理GPU 実行時間

(最適化後)GPU 実行時間

CPU実行時間(4 CORE)

CPU 実行時間(1 CORE)

CUDA初期化 87 ms 101 ms - -

カーネルコンパイル 530 ms 534 ms - -

文字列(PI)のロード 193 ms 194 ms 344 ms 334 ms

デバイスメモリアロケート 1 ms 1 ms - -

GPUへと文字列を転送 45 ms 45 ms - -

ヒストグラム作成 13 ms 38 ms 101 ms 348 ms

デバイスメモリ解放 < 1 ms < 1 ms - -

終了処理 85 ms 85 ms - -

総計 954 ms 998 ms 445 ms 682 ms

文字列検索

72

Java CUDA プログラミング

- JCudaの利用で、Javaで、CUDAアプリケーションを実装できる。

- CUDA Driver・Runtime APIを使用。

- 文字列検索、ヒストグラム作成とも、GPU上の処理は高速。

- 事前のカーネルコンパイル、メモリ転送量の削減など、工夫する必要あり。

(通常のCUDA C/C++プログラミングでも、同様の考慮を行っている。)

まとめ

73

NVIDIAからのお知らせ

74

NVIDIA ニュースレター

是非、ご登録ください。弊社からのアップデートを、定期的にお届けします。

http://www.nvidia.co.jp/object/newsletters-jp.html

75

CUDAを深く学びたい皆さまへ

2015年9月24日発売

インプレス社 5,400円(税込み)

Professional CUDA C Programmingの翻訳書

CUDA プログラミングモデルから各種メモリの特徴、ストリームの機能紹介

CUDA エンジニア 森野 慎也監訳

CUDA C プロフェッショナルプログラミング