maxwell と java cudaプログラミング
TRANSCRIPT
2
本資料について
本資料は、2016/1/27に実施された
「MaxwellとJava、C#のためのCUDA」の講演資料です。
本セミナーの詳細については、Compassのセミナーページをご参照ください。
http://nvidia.connpass.com/event/24764/
サンプルソースコードは、githubより、ご取得ください。
https://github.com/shin-morino/JCudaExamples
ご意見、ご質問などは、以下にお寄せください。
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.
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
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が、用途に応じ、無償で利用可能
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++ のソースコード
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];
}
}
デバッグ事前準備
41
Visual Studio側の操作
- 「プロセスにアタッチ…」を選択
- 以下を指定
トランスポート : Nsight GPU Debugger
修飾子 : localhost
- プロセスを選択
デバッグ
43
JavaでCUDAを使う
- 少々の工夫で、Javaでも、CUDAアプリケーションを実装できる。
- JCudaを用いて、CUDA Driver/Runtime APIを利用できる。
- JCudaを用いて、呼び出し。
- C/C++の場合、自動で行われる処理(初期化など)は、アプリケーション中に実装する必要あり。
- カーネルのデバッグも、ツールを使用して行う。
中間まとめ
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
検索位置
51
文字列検索
- 検索をスキップすることで高速化。
- 検索パターンの末尾から、文字ごとの比較を行う。
- 一致しない文字から、スキップする距離を取得
- 検索の先頭位置は、前回の検索結果に依存。
- 検索の先頭位置 = ”前回の先頭位置” + ”スキップする距離”
Boyer-Moore法
「逐次的」な処理CUDA・GPUでの並列化に向かない
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上での処理量を多く」する。
文字列検索
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
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++プログラミングでも、同様の考慮を行っている。)
まとめ
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 プロフェッショナルプログラミング