Upgrade to Pro — share decks privately, control downloads, hide ads and more …

プログラムを高速化する話Ⅱ ~GPGPU編~

プログラムを高速化する話Ⅱ ~GPGPU編~

GPUを利用して汎用演算を行う技術であるGPGPUを用いて、プログラムを高速化する技法についてまとめました。高速化の具体例も適宜用いて解説しています。
注: 京大マイコンクラブの春合宿2015で使ったスライドをそのまま掲載しているため、サンプルコードのリンクは利用できません。
CPU編はこちら https://speakerdeck.com/primenumber/puroguramuwogao-su-hua-suruhua

prime number

July 10, 2023
Tweet

More Decks by prime number

Other Decks in Programming

Transcript

  1. 自己紹介 KMC-ID: prime (KMC 5 回生 ) 京都大学理学部数学系 5 回生

    ( 卒業確定 ) KMC での活動 : 競技プログラミング ゲーム AI 作成 難解プログラミング言語 電子工作 37 代会計 root, 電子錠の管理 etc.
  2. この講座の内容 GPU: コンピューターの画像処理に使われる回路 graphics processing units の略 GPGPU: GPU を用いて様々な処理を行う

    General-purpose computing on GPU の略 GPU は CPU に比べて演算能力が高い CPU に比べ数倍〜数十倍高速なことも その性能を活かすためのテクニックを学ぶ 高速化の具体例もいくつか挙げていきます
  3. もくじ はじめに その高速化、必要ですか? GPGPU について CUDA 入門 CPU 編の復習 GPU

    特有の高速化テクニック CPU とも共通する高速化テクニック
  4. 最適化について 「細かい効率のことは忘れて、時間の 97% につ いて考え よう。時期尚早な最適化は諸悪の根源だ。 それでも残り 3% についても機会を逃すべきでは  ない」

    - Donald E. Knuth 「プログラム最適化の第一法則 : 最適化するな。 プログラム最適化の第二法則 ( 上級者限定 ): まだ するな。」 - Michael A. Jackson
  5. CPU と GPU の性能比較 名前 Core i7- 8700K Core i9-

    7890XE Ryzen Thread- Ripper 1950X Geforce GTX  1080Ti RX VEGA 64 単精度 (GFLOPS) 710.4 2995.2 870.4 10596.8 10436.6 倍精度 (GFLOPS) 355.2 1497.6 435.2 331.15 652.3 理論値/いずれもベースクロックでの値
  6. CPU と GPU の性能比較 名前 Xeon Platinum 8180 EPYC 7601

    Tesla V100 単精度 (GFLOPS) 4480 1126.4 14899.2 倍精度 (GFLOPS) 2240 563.2 7449.6 CPU/GPUとも1個あたりの値 理論値/CPUはベースクロックでの値 Tesla V100はベースクロック不明のため ブーストクロックでの値
  7. このスライドのベンチマークについて CPU は Core i7-6700K (Skylake) GPU は GeForce GTX

    1080 (Pascal) にてそれぞれ計測を行った 備考 メンテの関係で CPU メモリはシングルチャネルで動作 GPU を接続している PCIe レーンは x8
  8. GPU メーカーについて 主な GPU メーカーは 3 社 Intel CPU に内蔵された

    GPU(iGPU) のみ製造 AMD iGPU ・外付け (dGPU) ともに製造 NVIDIA dGPU のみ製造 CUDA は基本的に NVIDIA の GPU でのみ使える
  9. GPGPU の基本 プログラムから GPU を扱う方法はいろいろある CUDA OpenCL OpenACC OpenMP (>4.0)

    DirectX DirectCompute OpenGL Compute Shader 今回はサンプルには CUDA を使う
  10. CUDA とは NVIDIA が開発・提供している、 GPU 向けの汎用 並列コンピューティングプラットフォーム(並列コ ンピューティングアーキテクチャ)およびプログラ ミングモデルである。 -

    Wikipedia より CUDA C という言語を書くことで GPU 上で動く プログラムを書くことが出来る C++ とほぼ互換の言語 printf 等一部を除いて C++ 標準ライブラリは使えない
  11. CUDA 入門 main.cu #include <cstdio> __global__ void kernel() { printf("Hello,

    World!\n"); } int main() { kernel<<<1, 1>>>(); cudaDeviceSynchronize(); return 0; }
  12. CUDA 入門 CPU から呼ばれる GPU 側の関数(カーネル)には __global__ をつける 戻り値の型は void

    でなければならない 計算結果を CPU 側に伝えたい時はポインタを使う CPU 側から呼び出す時は kernel<<<1, 1>>>(); のように 三重山括弧を付けて呼び出す(数字の意味は後で) CPU から呼ばれる CPU 側の関数には __host__ 省略可能 GPU から呼ばれる GPU 側の関数には __device__
  13. CUDA 入門 ホスト (CPU) 側コード int a = 1, b

    = 2; int *a_d, *b_d, *c_d; cudaMalloc((void**)&a_d, sizeof(int)); cudaMalloc((void**)&b_d, sizeof(int)); cudaMalloc((void**)&c_d, sizeof(int)); cudaMemcpy(a_d, &a, sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(b_d, &b, sizeof(int), cudaMemcpyHostToDevice); kernel<<<1, 1>>>(a_d, b_d, c_d); int c; cudaMemcpy(&c, c_d, sizeof(int), cudaMemcpyDeviceToHost); std::cout << c << std::endl;
  14. CUDA 入門 GPU と CPU のメモリは違うので、 GPU 側メモリ を使いたいときには明示的に確保する必要がある CPU

    側から cudaMalloc 関数を使う CPU-GPU 間でデータをコピーする必要がある CPU 側から cudaMemcpy 関数を使う CPU→GPU の転送には cudaMemcpyHostToDevice GPU→CPU の転送には cudaMemcpyDeviceToHost を指定する cudaMemcpy は同期的に実行されるので cudaDeviceSynchronize は必要ない
  15. 例題:行列同士の積 N 行 M 列の行列 A と M 行 K

    列の行列 B の積 を計算する 備考:既に高速なライブラリが存在 あくまで例題 (a ij )(b ij )=(∑ k=0 M a ik b kj )=(c ij )
  16. 例題:行列同士の積 簡単のため、行列は 1 次元配列として確保 A, B, C をそれぞれ N*pitch1, M*pitch2,

    N*pitch3 個の長さを持つ配列とする pitch はコンピューターにとってキリの良い数 cudaMallocPitch 関数がやってくれる 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 A pitch1
  17. 例題:行列同士の積 CPU 上で素直に動かすコード for (size_t i = 0; i <

    N; ++i) { for (size_t j = 0; j < K; ++j) { for (size_t k = 0; k < M; ++k) { mat3[i*pitch2 + j] +=   mat1[i*pitch1 + k] * mat2[k*pitch2 + j]; } } }
  18. 例題:行列同士の積 サンプルコード デバイス (GPU) 側 __global__ void matmul_kernel_ver1( const float *

    const mat1, const float * const mat2, float * const mat3, const size_t pitch1, const size_t pitch2, const size_t pitch3, const size_t size1, const size_t size2, const size_t size3) { for (size_t i = 0; i < size1; ++i) { for (size_t j = 0; j < size3; ++j) { for (size_t k = 0; k < size2; ++k) { mat3[i*pitch3 + j] += mat1[i*pitch1 + k] * mat2[k*pitch2 + j]; } } } }
  19. 例題:行列同士の積 10 100 1000 10000 0.01 0.1 1 10 100

    1000 10000 100000 1000000 CPU(naive) CPU(optimized) GPU(naive) 行列のサイズ 実行時間 (ms)
  20. 例題:行列同士の積 行列積の定義 結果の行列 C の i, j に関して依存なく並列化出来る 各スレッドに固有の i,

    j を割り当て、要素数分並列化 GPU では 100 万スレッドでも容易に走らせられる ハードウェアスレッドのおかげ (a ij )(b ij )=(∑ k=0 M a ik b kj )=(c ij )
  21. 例題:行列同士の積 デバイス (GPU) 側 ( 引数部分は省略 ) const size_t i

    = blockIdx.y * blockDim.y + threadIdx.y; const size_t j = blockIdx.x * blockDim.x + threadIdx.x; if (i < size1 && j < size3) { for (size_t k = 0; k < size2; ++k) { mat3[i*pitch3 + j] += mat1[i*pitch1 + k] * mat2[k*pitch2 + j]; } }
  22. 例題:行列同士の積 ホスト (CPU) 側 size_t bv = (size1+threadsPerBlock-1) / threadsPerBlock;

    size_t bh = (size3+threadsPerBlock-1) / threadsPerBlock; dim3 block(threadsPerBlock, threadsPerBlock); dim3 grid(bh, bv); matmul_kernel_ver2<<<grid, block>>>( mat1_d, mat2_d, mat3_d, pitch1, pitch2, pitch3, size1, size2, size3);
  23. 例題:行列同士の積 ホスト (CPU) 側 size_t bv = (size1+threadsPerBlock-1) / threadsPerBlock;

    size_t bh = (size3+threadsPerBlock-1) / threadsPerBlock; dim3 block(threadsPerBlock, threadsPerBlock); dim3 grid(bh, bv); matmul_kernel_ver2<<<grid, block>>>( mat1_d, mat2_d, mat3_d, pitch1, pitch2, pitch3, size1, size2, size3);
  24. 例題:行列同士の積 10 100 1000 10000 0 0.01 0.1 1 10

    100 1000 10000 100000 1000000 CPU(naive) CPU(optimized) GPU(naive) GPU(parallel) 行列のサイズ 実行時間 (ms)
  25. CPU 編の復習:キャッシュの活用 メインメモリは遅い アクセスに数十〜数百 cycle かかる 帯域も CPU の演算能力に対して十分でない CPU

    コアに近いキャッシュを利用 容量は小さいが高速 L1 〜 L3 など容量と速度により階層化されている
  26. NVIDIA の GPU の構造 GPU は複数の SM(Streaming Multiprocesser) からなる 各

    SM は複数の CUDA Core からなる GPU SM CUDA Core
  27. GPU の性能と SM GPU の性能は SM の数とクロックとメモリ帯域等 で決まる GPU SMの数

    GTX1080Ti 28 GTX1080 20 GTX1070Ti 18 GTX1070 15 GTX1060 10 GTX1050Ti 6 GTX1050 5 GT1030 3
  28. Warp ブロックの中では 32 スレッドごとに塊を作る Warp という Warp の中ではスレッドは基本的に同じ命令を実行 SM の中の

    CUDA Core の組が同時に動いて実行 各スレッドが SIMD の各レーンに対応するようなもの Volta アーキテクチャだとちょっと違う ブロック内のスレッド数が 32 で割り切れないと 余った演算機は休んでしまう スレッド数は 32 の倍数が望ましい
  29. Warp スレッド 1 スレッド 2 スレッド 3 スレッド 4 スレッド

    5 スレッド 6 … スレッド 32 命令A 命令A 命令A 命令A 命令A 命令A 命令A 命令A 命令B 命令B 命令B 命令B 命令B 命令B 命令B 命令B 命令C 命令C 命令C 命令C 命令C 命令C 命令C 命令C 命令D 命令D 命令D 命令D 命令D 命令D 命令D 命令D
  30. コアレスアクセス メモリアクセスは Warp ごとにまとめられる A B C B A B

    C A A B C (A, B, Cのアドレスが離れていたら) 3回のメモリアクセスになる
  31. 例題:二次元配列のコピー 1000 10000 100000 0.01 0.1 1 10 100 1000

    GPU(column-major) GPU(row-major) width = height 実行時間 (ms)
  32. メモリ帯域と演算能力 GTX1080 の演算能力は約 9TFLOPS 1 秒間に 9 兆回計算できる 実際には 4.5

    兆回の積和算 (A*B+C) ができる foat は 4byte 、積和算は 3 つの値を読み込む 単純に考えると 4bytes*3*4.5T/s=54TB/s 必要 一方メモリ帯域は 320GB/s 全然足りない!
  33. 例題:行列同士の積 行列が N 行 N 列の正方行列とすると、 読み込む必要があるのは A, B それぞれ

    N^2 個 計算する必要があるのは約 2*N^3 回 うまくキャッシュ出来れば、高速化出来る ここではブロック化という方法を使う
  34. 例題:行列同士の積 同時に i+1 行 j 列を計算すると、 = × c ij

    a i 1 b 1 j 2回の積和算に3回読み込み c i+1 j a i+11
  35. 例題:行列同士の積 列に関しても同じことをすると、 = × c ij a i 1 b

    1 j 4回の積和算に4回読み込み c i+1 j a i+11 c ij+1 c i+1 j +1 b 1 j +1
  36. 例題:行列同士の積 サンプルのため端数処理は省略 __shared__ float localA[threadsPerBlock][threadsPerBlock]; __shared__ float localB[threadsPerBlock][threadsPerBlock]; float tmp

    = 0.0; for (size_t k = 0; k < size2; k += threadsPerBlock) { __syncthreads(); localA[threadIdx.y][threadIdx.x] = mat1[i*pitch1 + (k + threadIdx.x)]; localB[threadIdx.y][threadIdx.x] = mat2[(k + threadIdx.y)*pitch2 + j]; __syncthreads(); for (size_t k2 = 0; k2 < threadsPerBlock; ++k2) { tmp += localA[threadIdx.y][k2] * localB[k2][threadIdx.x]; } } mat3[i*pitch3 + j] = tmp;
  37. 例題:行列同士の積 同期をとらないと シェアードメモリへの 書き込み シェアードメモリへの 書き込み シェアードメモリへの 書き込み シェアードメモリから 読み込み

    シェアードメモリから 読み込み 意図しない動作 スレッドA スレッドB ←まだ書き込みしていない ところにアクセス
  38. 例題:行列同士の積 10 100 1000 10000 0 0.01 0.1 1 10

    100 1000 10000 100000 CPU(optimized) GPU(parallel) GPU(shared memory) 行列のサイズ 実行時間 (ms)
  39. シェアードメモリのバンクコンフリクト 各ワード (4byte) は順番に各バンクに割り振られて いる バンク 0 1 2 3

    4 5 6 7 ワード 0 1 2 3 4 5 6 7 ワード 32 33 34 35 36 37 38 39 ワード 64 65 66 67 68 69 70 71 …
  40. コンスタントメモリの活用 各 SM にコンスタントキャッシュがある コンスタントメモリが使えるとき GPU から値を書き換えない 64KB 以内 Warp

    内で同じ値を使うときに高速になる そうでないときはあまり速くない シェアードメモリ同様うまく使えれば高速 π などの定数を格納するのに使える
  41. 同時実行できる Warp 数の制限 SM 内で共有されるリソース レジスタファイル シェアードメモリ 例えば GTX1080 ではシェアードメモリは

    96KB/SM 1Warp あたり 10KB なら同時実行できるのは最大 9Warp 1 ブロックあたりシェアードメモリの使用量を S 、 レジスタファイル使用量を R 、ブロックあたりの Warp 数を W とすると min(64, 32*W, 96K/S, 256K/R)Warp 同時実行可能
  42. Occupancy 実際に同時実行可能な Warp 数 /64( 最大値 ) を Occupancy (占有率)という

    Occupancy を高めるのが効率を上げる方法の1つ スレッド当たりシェアードメモリの使用量を減らすなど
  43. 例題: N-Queen N*N のマス目上に N 個のクイーンを互いに効きが ないように置く場合の数を求める Q \ ー

    ー ー Q Q / / Q \ | / Q ー ー Q ー ー ー Q / | \ / | \ Q | Q \ Q | N=4: 2通り N=6: 8通り
  44. 例題: N-Queen 縦の効きを考えると各列に置けるクイーンは 1 個 同様に、各行に置けるのも1個 斜めの効きを考えなければ 組み合わせは n! 通りある

    1 列ずつ決めていって、 可能な解を探索できる バックトラックという | ー Q ー ー Q ー | | | Q ダメな例
  45. 例題: N-Queen CPU( シングルスレッド ) 版 uint64_t solve(int N, int

    depth = 0, uint32_t left = 0, uint32_t mid = 0, uint32_t right = 0) { if (depth == N) return 1; uint64_t sum = 0; for (uint32_t pos = (((uint32_t)1 << N) - 1) & ~(left | mid | right); pos; pos &= pos-1) { uint32_t bit = pos & -pos; sum += solve(N, depth+1, (left | bit) << 1, mid | bit, (right | bit) >> 1); } return sum; }
  46. 例題: N-Queen GPU 版 __global__ void kernel(const int N, const

    int depth, const uint32_t * const left_ary, const uint32_t * const mid_ary, const uint32_t * const right_ary, uint64_t * const result_ary, const size_t size) { int index = threadIdx.x + blockIdx.x * blockDim.x; if (index < size) { result_ary[index] = solve(N, depth, left_ary[index], mid_ary[index], right_ary[index]); } }
  47. 例題: N-Queen 9 10 11 12 13 14 15 16

    17 18 0 0 0.01 0.1 1 10 100 CPU(naive) CPU(parallel) GPU(naive) N 実行時間 (s)
  48. 例題: N-Queen solve 関数 __host__ __device__ uint64_t solve(int N, int

    depth = 0, uint32_t left = 0, uint32_t mid = 0, uint32_t right = 0) { if (depth == N) return 1; uint64_t sum = 0; for (uint32_t pos = (((uint32_t)1 << N) - 1) & ~(left | mid | right); pos; pos &= pos-1) { uint32_t bit = pos & -pos; sum += solve(N, depth+1, (left | bit) << 1, mid | bit, (right | bit) >> 1); } return sum; }
  49. 例題: N-Queen スタックで持つべき状態 depth, left, mid, right, pos の 5

    つ 再帰とループと同じことを1つのループで実現する 各ループで、 pos から 1bit とり、スタックに新しい状態を push pos から取れるビットがなければ pop スタックが空になったら終了
  50. 例題: N-Queen 例 depth left mid right pos 3 0b0010

    0b1011 0b0010 0b0100 2 0b1000 0b1010 0b0100 0b0000
  51. 例題: N-Queen 例 depth left mid right pos 3 0b0010

    0b1011 0b0010 0b0100 2 0b1000 0b1010 0b0100 0b0000
  52. 例題: N-Queen 例 depth left mid right pos 4 0b1100

    0b1111 0b0011 0b0000 3 0b0010 0b1011 0b0010 0b0000 2 0b1000 0b1010 0b0100 0b0000
  53. 例題: N-Queen 例 depth left mid right pos 4 0b1100

    0b1111 0b0011 0b0000 3 0b0010 0b1011 0b0010 0b0000 2 0b1000 0b1010 0b0100 0b0000 ++Count
  54. 例題: N-Queen 例 depth left mid right pos 3 0b0010

    0b1011 0b0010 0b0000 2 0b1000 0b1010 0b0100 0b0000
  55. 例題: N-Queen 9 10 11 12 13 14 15 16

    17 18 0.0001 0.0010 0.0100 0.1000 1.0000 10.0000 100.0000 CPU(naive) CPU(parallel) GPU(naive) GPU(optimized) N 実行時間 (s)
  56. メモリ転送とカーネル実行の並列化 cudaStream_t str[3]; for (int i = 0; i <

    3; ++i) { cudaStreamCreate(str + i); cudaMemcpyAsync(a_d + n*i, a_h + n*i, sizeof(int) * n, cudaMemcpyHostToDevice, str[i]); kernel<<<1024, 256, 0, str[i]>>>(a_d + n*i, b_d + n*i, n); cudaMemcpyAsync(b_h + n*i, b_d + n*i, sizeof(int) * n, cudaMemcpyDeviceToHost, str[i]); } for (int i = 0; i < 3; ++i) { cudaStreamSynchronize(str[i]); cudaStreamDestroy(str[i]); }
  57. ソフトウェアパイプライニング for (int i = 0; i < n; ++i)

    { A(i); B(i); } 各 i に対し A(i), B(i) に依存関係があるとする A(i) と A(i+1), A(i+2),... 、 B(i) と B(i+1), B(i+2),... には依存関係がないとする A(i), B(i) にそれなりのレイテンシがあるとすると
  58. ソフトウェアパイプライニング for (int i = 0; i < n; ++i)

    { A(i); } for (int i = 0; i < n; ++i) { B(i); } とすることで
  59. 例題:行列同士の積 シェアードメモリを使っているので既に Occupancy は低い ソフトウェアパイプライニングするために、 1 スレッドで 8x8 要素分の答えを同時に計算する 同時にレジスタブロッキングでメモリアクセスを減らす

    ブロック全体では 64*64 のブロック化になる 4096 回の積和算に対し 128 回の読み込み ループ 8 回分の読み込みを同時に行うことで、コアレス アクセスにしてさらに高速化
  60. 例題:行列同士の積 10 100 1000 10000 0 0.01 0.1 1 10

    100 1000 10000 100000 CPU(optimized) GPU(parallel) GPU(shared memory) GPU(register blocking) 行列のサイズ 実行時間 (ms)
  61. アトミック命令 次の操作を考える Load A to x x = x+1 Store

    x to A 操作の後 A の値は 1 増える これを二並列で実行したら、当然 2 増えてほしい
  62. アトミック命令 マルチスレッドで同じ場所に対して読み書きすると Load A to x スレッド1 Load A to

    y x = x+1 y = y+1 Store x to A Store y to A スレッド2 Aに対して2回足したはず なのに1しか増えない!
  63. 例題:合計値の計算 デバイス側コード __global__ void sum_vec_naive( unsigned long long * result)

    { int i = threadIdx.x + blockIdx.x * threadsPerBlock; atomicAdd(result, i + 1); }
  64. 例題:合計値の計算 20 21 22 23 24 25 26 27 28

    29 30 1 10 100 1000 GPU(naive) CPU(naive) M 実行時間 (ms)
  65. 例題:合計値の計算 int i = threadIdx.x + blockIdx.x * threadsPerBlock; __shared__

    unsigned long long tmp[threadsPerBlock]; tmp[threadIdx.x] = i + 1; __syncthreads(); for (uint32_t s = 1; s < threadsPerBlock; s *= 2) { if (threadIdx.x % (2*s) == 0) { tmp[threadIdx.x] += tmp[threadIdx.x + s]; } __syncthreads(); } if (threadIdx.x == 0) { atomicAdd(result, tmp[0]); }
  66. 例題:合計値の計算 20 21 22 23 24 25 26 27 28

    29 30 0.1 1 10 100 1000 GPU(naive) CPU(naive) GPU(reduction) M 実行時間 (ms)
  67. 例題:合計値の計算 int i = threadIdx.x + blockIdx.x * threadsPerBlock; __shared__

    unsigned long long tmp[threadsPerBlock]; tmp[threadIdx.x] = i + 1; __syncthreads(); for (uint32_t s = threadsPerBlock/2; s > 0; s >>= 1) { if (threadIdx.x < s) { tmp[threadIdx.x] += tmp[threadIdx.x + s]; } __syncthreads(); } if (threadIdx.x == 0) { atomicAdd(result, tmp[0]); }
  68. 例題:合計値の計算 変更前 5 8 3 2 9 1 6 4

    13 5 10 10 + + + + 18 20 + + + 38 ⓪ ② ④ ⑥ ⓪ ④ ⓪
  69. 例題:合計値の計算 変更後 5 8 3 2 9 1 6 4

    14 9 9 6 23 15 38 ⓪ ② ⓪ ① ⓪ ① ③
  70. 例題:合計値の計算 更に最適化していく 半数のスレッドは 1 回も reduction に参加しない ブロックあたりのスレッド数を半分にする 代わりに、 1

    スレッドで 2 個の和を計算する ブロックあたりのスレッド数を固定すると、 ループを展開できる ループの条件分岐が不要になる
  71. 例題:合計値の計算 int i = threadIdx.x + blockIdx.x * threadsPerBlock; __shared__

    unsigned long long tmp[halfOfThreadsPerBlock]; tmp[threadIdx.x] = (i + 1) + (i + halfOfThreadsPerBlock + 1); __syncthreads(); if (threadIdx.x < 64) { tmp[threadIdx.x] += tmp[threadIdx.x + 64]; } 続く
  72. 例題:合計値の計算 続き __syncthreads(); if (threadIdx.x < 32) { tmp[threadIdx.x] +=

    tmp[threadIdx.x + 32]; __syncwarp(); tmp[threadIdx.x] += tmp[threadIdx.x + 16]; __syncwarp(); tmp[threadIdx.x] += tmp[threadIdx.x + 8]; __syncwarp(); tmp[threadIdx.x] += tmp[threadIdx.x + 4]; __syncwarp(); tmp[threadIdx.x] += tmp[threadIdx.x + 2]; __syncwarp(); tmp[threadIdx.x] += tmp[threadIdx.x + 1]; } if (threadIdx.x == 0) { atomicAdd(result, tmp[0]); }
  73. 例題:合計値の計算 20 21 22 23 24 25 26 27 28

    29 30 0.1 1 10 100 1000 GPU(naive) CPU(naive) GPU(reduction) GPU(bank conflict free) GPU(2 number/thread) GPU(loop unroll) M 実行時間 (ms)
  74. プロファイラを使おう プロファイリングをすることで、どこが ボトルネックになっているか見極め手助けになる デバイス側までプロファイリングするときは、 nvprof または NVIDIA Visual Profler が使える

    プロファイラでは各カーネルの実行時間や、 メモリ転送効率、 Occupancy などの情報を 収集できる プロファイラの使い方等はこの講座の範囲を超える ので省略
  75. 参考文献 [0] CUDA Toolkit Documentation http://docs.nvidia.com/cuda/index.html [1] CUDA C Programming

    Guide http://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html [2] Better Performance at Lower Occupancy http://www.nvidia.com/content/GTC-2010/pdfs/2238_GTC2010.pdf
  76. 参考文献 [3] CUDA By Example 汎用 GPU プログラミング入門 インプレスジャパン ISBN:

    978-4-8443-2978-7 [4] CUDA C プロフェッショナルプログラミング インプレス ISBN: 978-4-8443-3891-8 [5] はじめての CUDA プログラミング 工学社 ISBN: 978-4-7775-1477-9