570 likes | 696 Vues
HPC アプリケーションの OOP を用いた パフォーマンスチューニング. 東京大学 大学院 創造情報学専攻 穂積 俊平 * 伊尾木 将之 千葉 滋. HPC の現状. C 言語や Fortran によって記述されている 実行性能が重要 手続きライブラリが広く利用されている 関数単位での変更のみ可能. HPC の近年の傾向. 実行環境に合わせたチューニングが必要 実行環境のハードウェアが多様化. GPU. コンピュータ クラスタ. マルチ CPU. HPC の近年の傾向. 実行環境に合わせたチューニングが必要 実行環境のハードウェアが多様化. GPU.
E N D
HPCアプリケーションのOOPを用いたパフォーマンスチューニングHPCアプリケーションのOOPを用いたパフォーマンスチューニング 東京大学大学院 創造情報学専攻 穂積俊平* 伊尾木将之 千葉滋
HPCの現状 • C言語やFortranによって記述されている • 実行性能が重要 • 手続きライブラリが広く利用されている • 関数単位での変更のみ可能
HPCの近年の傾向 • 実行環境に合わせたチューニングが必要 • 実行環境のハードウェアが多様化 GPU コンピュータ クラスタ マルチCPU
HPCの近年の傾向 • 実行環境に合わせたチューニングが必要 • 実行環境のハードウェアが多様化 GPU コンピュータ クラスタ マルチCPU
HPCの問題点 • 粒度の細かいチューニングができない • 手続きライブラリはブラックボックスであり、関数より粒度の細かい変更が不可能。 入力 出力 メモリ管理 データ転送
HPCの問題点 • 粒度の細かいチューニングができない • 手続きライブラリはブラックボックスであり、関数より粒度の細かい変更が不可能。 入力 出力 処理(関心事)ごとに実装を 決めたい! メモリ管理 データ転送
例:行列積 C B A M N N M L L ① ② Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} ③ = *
例:行列積 C B A M N N • 素直な2重ループ • iと j を入れ替えた2重ループ • GPUで並列実行 • MPIで並列実行 M L L ① Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} = * j i
例:行列積 C B A M N N • 素直なループ • 展開したループ M L L ② Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} = * k k
例:行列積 A M • 1次元配列 • 2次元配列 • シェアードメモリ • 疎行列 • 対角行列 L Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} ③ 1次元配列 M ・・・
C言語での実装 • 保守性のスケーラビリティが得られない • 関数ポインタ • ifdef
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード Matmulmatmul = new Matmul(); matmul.setTraverse (new Traverse()); matmul.setReduction(new Reduction()); Mat A = new SingleArray(), B =・・・, C =・・・; matmul.calc(A, B, C); ① ② ③
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード Matmulmatmul = new Matmul(); matmul.setTraverse (new ParallelOnGPU()); matmul.setReduction(new Reduction()); Mat A = new SingleArray(), B =・・・, C =・・・; matmul.calc(A, B, C); ① ② ③
オートチューニングへの応用 • 実装の切り替えが容易 Javaコード List<Traverse> traverses; traverses.add(new Traverse()); traverses.add(new ParallelOnGPU()); : for(Traverse tra: traverses) { matmul.setTraverse(tra); matmul.calc(A, B, C); } Cのたどり方の実装を 変更して実行
HPCアプリケーションの特徴 • カーネルコードが計算時間の大半を占める 実行過程 実行時間 各種設定 カーネル コード
WootinJ • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする • ユーザに最適化の条件を満たしているかを提示 実行時 コンパイラ コンパイル時 チェッカー Java ソースコード Java バイトコード 最適化 機械語 警告!
通常のJITとの違い • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする 一部をインライン化 • オブジェクト • メソッド • 静的型 • 動的型 最適化 Java バイトコード 機械語
通常のJITとの違い • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする • 静的型 • 動的型 • strictfinal 全てをインライン化 • オブジェクト • メソッド 最適化 Java バイトコード 機械語
WootinJ • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする • ユーザに最適化の条件を満たしているかを提示 • strictFinal :静的に型が一意に決定できる型 • プリミティブな型 • strictFinalの配列 • 自分と親クラスのフィールドがstrictFinalであり、finalな型
WootinJ • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする • ユーザに最適化の条件を満たしているかを提示 strictFinal ? Java ソースコード 警告!!
WootinJ • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする メソッド情報 ユーザが指定したメソッド Javaコード Java バイトコード static void main(String[] args) { CUDARunner.invoke( new Calc(), “run”, new A() ); } Java 抽象構文木 最適化 class Calc { void run(A a){ a.hoge(); } } CUDA コード 実行 機械語
WootinJ • OOPと実行性能を両立する機構 • カーネルコードを強力にJITコンパイルする メソッド情報 ユーザが指定したメソッド Javaコード メソッドのレシーバ、実引数の 型は変換時に一意に決定できる メソッドのレシーバ、実引数は 自由にOOPの抽象化を利用できる static void main(String[] args) { CUDARunner.invoke( new Calc(), “run”, new A() ); } class Calc { void run(A a){ a.hoge(); } }
実験 • 目的 • WootinJの実行性能の測定 • 実験環境 • Tsubame2.0 : 東工大のスパコン • プログラム • 行列積
実験結果 • 変換によるオーバーヘッドの分だけWootinJの方が遅かったが、OOPの抽象化による差は見られなかった。 良
関連研究 • Firepile[Nathaniel Nystromら・2011] • ScalaからOpenCLへの実行時変換器。動的束縛はSwitch文を用いて表現 • Aparapi • JavaからOpenCLへの実行時変換器。オブジェクトの利用はできない
まとめと今後の課題 • まとめ • オブジェクト指向を利用する事で、実行環境に合わせたパフォーマンスチューニングができる事を述べた。 • オブジェクト指向と実行性能を両立する機構としてWootinJを開発した。 • 今後の課題 • WootinJを利用したフレームワークの作成 • C++との比較
Cの要素のたどり方 for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k]*B[k * N + j]; } } } ① GPUを利用した並列実行 iとjを入れ替えた2重ループ 素直な2重ループ dim3 grid(N/BS, L/BS), block(BS,BS); traverse<<<grid, block>>>(A, B, C); _global_ void traverse(float *A・・){ inti = BS*blockIdx.y + threadIdx.y; int j = BS*blockIdx.x + threadIdx.x; : } for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { : } } for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { : } }
Cの1要素の求め方 素直なループ 展開したループ for (int k = 0; k < M; k++) { C[i*N + j] += A[i*M + k]*B[k*N + j]; } for (int k = 0; k < M; k+=2) { C[i*N + j] += A[i*M + k] * B[k*N + j]; C[i*N + j] += A[i*M + k + 1] * B[(k+1)*N + j]; } for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k]*B[k * N + j]; } } } ②
行列の表現方法 • 言語上の確保の仕方の違い • 1次元配列 • 2次元配列 • ハードウェア的な違い • シェアードメモリの利用 • 行列の種類による違い • 疎行列 • 対角行列
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード Matmulmatmul = new Matmul(); matmul.iterate = new LoopOnCPU(); matmul.reduction= new Reduction(); Matrix A = new SingleArray(), B =・・・, C =・・・; matmul.calc(A, B, C); ① ② ③
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード Matmulmatmul = new Matmul(); matmul.iterate = new LoopOnCPU(); matmul.reduction= new UnrollingReduction(); Matrix A = new SingleArray(), B =・・・, C =・・・; matmul.calc(A, B, C); ① ② ③
実行環境に合わせたチューニング • ユーザによる設定ができる形で機能提供すべき • C言語やFortranでは難しい • 手続きライブラリはブラックボックス • ブラックボックス内の実装の変更が困難 入力 出力 複数の処理 (関心事)が 含まれる メモリ管理 データ転送
行列積に含まれる関心事 C B A M N N • 複数の関心事に分割できる M L L ① Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} = * j i
行列積に含まれる関心事 C B A M N N • 複数の関心事に分割できる M L L ② Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} = * k k
行列積に含まれる関心事 A M • 複数の関心事に分割できる L Cコード for (inti = 0; i < L; i++) { for (int j = 0; j < N; j++) { for (int k = 0; k < M; k++) { C[i* N + j] += A[i * M + k] * B[k * N + j]; }}} ③ 1次元配列 M ・・・
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード Matmulmatmul = new Matmul(); matmul.setTraverse(Traverse.factory(“simpleLoop”)); matmul.setReduction(Reduction.factory(“unrolling”)); Matrix A = new SingleArray(), B =・・・, C =・・・; matmul.calc(A, B, C); ① ② ③
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Matmul void calc(Mat A, Mat B, Mat C) { tra.calc(A, B, C, red); } Traverse ReverseTraverse void calc(Mat A, Mat B, Mat C, Reduction red) { for(inti = 0; i < C.getRows(); i++) { for(int j = 0; j < C.getCols(); j++) { red.calc(A, B, C); }}} void calc(Mat A, Mat B, Mat C, Reduction red) { for(int j = 0; j < C.getCols(); j++) { for(inti = 0; i < C.getRows(); i++) { red.calc(A, B, C); }}} Reduction void calc(Mat A, Mat B, Mat C){ for(int k = 0; k < A.getCols(); k++) { C[i*C.getCols()+j] += A[i*A.getCols()+k] * B[k*B.getCols()+j]; }}
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Matmul void calc(Mat A, Mat B, Mat C) { tra.calc(A, B, C, red); } Traverse ReverseTraverse void calc(Mat A, Mat B, Mat C, Reduction red) { for(inti = 0; i < C.getRows(); i++) { for(int j = 0; j < C.getCols(); j++) { red.calc(A, B, C); }}} void calc(Mat A, Mat B, Mat C, Reduction red) { for(int j = 0; j < C.getCols(); j++) { for(inti = 0; i < C.getRows(); i++) { red.calc(A, B, C); }}}
近年の傾向 • 実行環境に合わせたチューニングが必要 • ハードウェアが複雑化している • GPU • コンピュータ・クラスタ • マルチコアCPU GPU
関数による分離 Cコード void matmul(float *A, float *B, float *C) { traverse(A, B, C); } void traverse(float *A, float *B, float *C) { for(inti = 0; i < L; i++) { for(int j = 0; j < N; j++) { reduction(A, B, C, i, j); }}} void reduction(float *A, float *B, float *C, inti, int j) { for(int k = 0; k < M; k++) { C[i * N + j] += A[i * M + k] * B[k * N + j]; }}
手続きライブラリの限界 • 粒度の細かいチューニングができない • 手続きライブラリはブラックボックスであり、関数より粒度の細かい変更が不可能。 他の実装へ 変更 入力 出力 メモリ管理 データ転送
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード class Matmul { Traverse traverse; Reduction reduction; void calc(Matrix A, Matrix B, Matrix C) { traverse.calc(A, B, C, reduction); } }
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード class SimpleDoubleLoop implements Traverse { void calc(Matrix A, Matrix B, Matrix C, Reduction red) { for(inti = 0; i < C.getRows(); i++) { for(int j = 0; j < C.getColumns(); j++) { reduction.calc(A, B, C); } } } }
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Javaコード class SimpleLoop implements Reduction { void calc(Matrix A, Matrix B, Matrix C){ for(int k = 0; k < A.getColumns(); k++) { C[i*C.getColumns()+j] += A[i*A.getColumns()+k] * B[k*B.getColumns()+j]; } } }
オブジェクト指向を用いたチューニング • ユーザによる設定が可能な形で機能提供ができる Matmul Traverse Reduction void calc(Matrix A, Matrix B, Matrix C) { traverse.calc(A, B, C, reduction); } void calc(Matrix A, Matrix B, Matrix C, Reduction red) { for(inti = 0; i < C.getRows(); i++) { for(int j = 0; j < C.getColumns(); j++) { reduction.calc(A, B, C); }}} void calc(Matrix A, Matrix B, Matrix C){ for(int k = 0; k < A.getColumns(); k++) { C[i*C.getColumns()+j] += A[i*A.getColumns()+k] * B[k*B.getColumns()+j]; }}
関数ポインタによる変更 void traverse(float *A, float *B, float *C) { for(inti = 0; i < L; i++) { for(int j = 0; j < N; j++) { (redP)(A, B, C, i, j); }}} Cコード int (*traP)(float *A, float *B, float *C) = &traverse; int (*redP)(float *A, float *B, float *C, inti, int j) = &reduction; void matmul(float *A, float *B, float *C) { (traP)(A, B, C); } void traverse(float *A, float *B, float *C) { for(inti = 0; i < L; i++) { for(int j = 0; j < N; j++) { (redP)(A, B, C, i, j); }}} void reduction(float *A, float *B, float *C, inti, int j) { for(int k = 0; k < M; k++) { C[i * N + j] += A[i * M + k] * B[k * N + j]; }} void reverseTraverse(float *A, float *B, float *C) { for(inti = 0; i < L; i++) { for(int j = 0; j < N; j++) { (redP)(A, B, C, i, j); }}}
関数による分離 Cコード void matmul(float *A, float *B, float *C) { traverse(A, B, C); } void traverse(float *A, float *B, float *C) { for(inti = 0; i < L; i++) { for(int j = 0; j < N; j++) { reduction(A, B, C, i, j); }}} void reduction(float *A, float *B, float *C, inti, int j) { for(int k = 0; k < M; k++) { C[i * N + j] += A[i * M + k] * B[k * N + j]; }}
手続きライブラリの利用 • 実行環境に合ったライブラリを利用する事で、アプリケーションのチューニングができる。 線形代数ライブラリ • GotoBLAS • cublas • ・・・・ CPU GPU
関数ポインタによる変更 int(*traP)(float *A, float *B, float *C); int (*redP)(float *A, float *B, float *C, inti, int j); void matmul(float *A, float *B, float *C) { (traP)(A, B, C); } void reverseTraverse(float *A, float *B, float *C) { for(int j = 0; j < N; j++) { for(inti = 0; i < L; i++) { (redP)(A, B, C, i, j); }}} void traverse(float *A, float *B, float *C) { for(inti = 0; i < L; i++) { for(int j = 0; j < N; j++) { (redP)(A, B, C, i, j); }}}
C言語による実装の限界 • 関数ポインタ • 安全でない • フラグによる制御 • ライブラリ作成者がすべての状況を想定できない