Weitere ähnliche Inhalte
Ähnlich wie 20190625 OpenACC 講習会 第3部 (20)
Mehr von NVIDIA Japan (20)
Kürzlich hochgeladen (11)
20190625 OpenACC 講習会 第3部
- 1. 第3部 – OpenACC によるループの最適化
Naruhiko Tan, Solution Architect, NVIDIA
OPENACC 講習会
- 7. while ( err > tol && iter < iter_max ) {
err=0.0;
#pragma acc parallel loop reduction(max:err)
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
Anew[OFFSET(j, i, m)] = 0.25 * ( A[OFFSET(j, i+1, m)] + A[OFFSET(j, i-1, m)]
+ A[OFFSET(j-1, i, m)] + A[OFFSET(j+1, i, m)]);
error = fmax( error, fabs(Anew[OFFSET(j, i, m)] - A[OFFSET(j, i , m)]));
}
}
#pragma acc parallel loop
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
A[OFFSET(j, i, m)] = Anew[OFFSET(j, i, m)];
}
}
iter++;
}
OPENACC PARALLEL LOOP による並列化
7
最初のループを並列化
max reduction を指定
2つ⽬のループを並列化
どのループを並列化するかのみを指⽰し、
どのように並列化するかの詳細は指⽰
していない。
- 8. #pragma acc data copy(A[0:n*m]) copyin(Anew[0:n*m])
while ( err > tol && iter < iter_max ) {
err=0.0;
#pragma acc parallel loop reduction(max:err) copyin(A[0:n*m]) copy(Anew[0:n*m])
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
Anew[OFFSET(j, i, m)] = 0.25 * ( A[OFFSET(j, i+1, m)] + A[OFFSET(j, i-1, m)]
+ A[OFFSET(j-1, i, m)] + A[OFFSET(j+1, i, m)]);
error = fmax( error, fabs(Anew[OFFSET(j, i, m)] - A[OFFSET(j, i , m)]));
}
}
#pragma acc parallel loop copyin(Anew[0:n*m]) copyout(A[0:n*m])
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
A[OFFSET(j, i, m)] = Anew[OFFSET(j, i, m)];
}
}
iter++;
}
最適化されたデータ転送
必要な時のみ A をコピー。
Anew の初期条件はコピーす
るが、最終的な値はしない。
- 18. ここでのペインターは OpenACC
で⾔うところの worker で、 あ
る⼀定の作業量しかこなせない。
ペインターのローラーは vector
のようなもので、1度により広い
壁をカバーすることで、より作業
量をこなすことができる。
さらに作業量を増やすためには、
gangs に組織される worker
がさらに必要。
Workers
Gang
Vector
はじめての GANGS, WORKERS, AND VECTORS
- 20. GPU コードのプロファイリング (PGPROF)
§ pgprof は、GPU コードであれば、さらに
多くの情報を取得することができる。
§ CPU details, GPU details に加えて、タ
イムラインを確認することができる上に、パ
フォーマンスの分析も可能。
pgprof による GPU コードのプロファイリング
- 21. § MemCpy(HtoD): ホストからデバイスへの
データ転送 (CPU to GPU)
§ MemCpy(DtoH): デバイスからホストへの
データ転送 (GPU to CPU)
§ Compute: GPU での演算処理。
calcNext, swap 関数を確認することが
できる。
GPU コードのプロファイリング (PGPROF)
pgprof による GPU コードのプロファイリング
- 22. § MemCpy(HtoD): ホストからデバイスへの
データ転送 (CPU to GPU)
§ MemCpy(DtoH): デバイスからホストへの
データ転送 (GPU to CPU)
§ Compute: GPU での演算処理。
calcNext, swap 関数を確認することが
できる。
GPU コードのプロファイリング (PGPROF)
pgprof による GPU コードのプロファイリング
- 23. § MemCpy(HtoD): ホストからデバイスへの
データ転送 (CPU to GPU)
§ MemCpy(DtoH): デバイスからホストへの
データ転送 (GPU to CPU)
§ Compute: GPU での演算処理。
calcNext, swap 関数を確認することが
できる。
GPU コードのプロファイリング (PGPROF)
pgprof による GPU コードのプロファイリング
- 27. OPENACC LOOP ディレクティブ
§ 直後の1つのループの並列化を指⽰。
§ ループに関する追加の情報や、最適化の⽅法を指
定することができる。
§ ループに適⽤する様々な並列性の記述⽅法を提
供。
§ OpenACC コンピュート領域 (kernels or parallel
領域) の中で使われなければならない。
並列性の表現
C/C++
#pragma acc loop
for(int i = 0; i < N; i++)
// Do something
Fortran
!$acc loop
do i = 1, N
! Do something
- 28. COLLAPSE クローズ
§ collapse( N )
§ 直後の N 個の tightly nested loops を結
合。
§ 多次元のループ ネスト1次元のループに変換
することができる。
§ ループ⻑を⻑くして、より多くの並列性を抽出
したり、メモリの局所性を向上させるのに有効。
#pragma acc parallel loop collapse(2)
for( i = 0; i < size; i++ )
for( j = 0; j < size; j++ )
double tmp = 0.0f;
#pragma acc loop reduction(+:tmp)
for( k = 0; k < size; k++ )
tmp += a[i][k] * b[k][j];
c[i][j] = tmp;
- 29. for( i = 0; i < 4; i++ )
for( j = 0; j < 4; j++ )
array[i][j] = 0.0f;
COLLAPSE クローズ
(0,0) (0,1) (0,2) (0,3)
(1,0) (1,1) (1,2) (1,3)
(2,0) (2,1) (2,2) (2,3)
(3,0) (3,1) (3,2) (3,3)
collapse( 2 )
#pragma acc parallel loop collapse(2)
for( i = 0; i < 4; i++ )
for( j = 0; j < 4; j++ )
array[i][j] = 0.0f;
- 31. COLLAPSE クローズ
#pragma acc data copy(A[:n*m]) copyin(Anew[:n*m])
while ( err > tol && iter < iter_max ) {
err=0.0;
#pragma acc parallel loop reduction(max:err) collapse(2)
copyin(A[0:n*m]) copy(Anew[0:n*m])
for( int j = 1; j < n-1; j++) {
for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] +
A[j-1][i] + A[j+1][i]);
err = max(err, abs(Anew[j][i] - A[j][i]));
}
}
#pragma acc parallel loop collapse(2)
copyin(Anew[0:n*m]) copyout(A[0:n*m])
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
A[j][i] = Anew[j][i];
}
}
iter++;
}
並列化の⾃由度を上げるため
に、2つのループを1つに
collapse
- 33. TILE クローズ
§ tile ( x , y , z, ...)
§ 多次元ループを、”tiles” または “blocks” に
分割。
§ データ局所性を向上させられる場合がある。
§ 複数の “tiles” を同時に実⾏可能。
#pragma acc kernels loop tile(32, 32)
for( i = 0; i < size; i++ )
for( j = 0; j < size; j++ )
for( k = 0; k < size; k++ )
c[i][j] += a[i][k] * b[k][j];
- 34. TILE クローズ
(0,0) (0,1) (0,3)(0,2)
(1,0) (1,1) (1,3)(1,2)
(2,0) (2,1) (2,3)(2,2)
(3,0) (3,1) (3,3)(3,2)
for(int x = 0; x < 4; x++){
for(int y = 0; y < 4; y++){
array[x][y]++;
}
}
#pragma acc kernels loop tile(2,2)
for(int x = 0; x < 4; x++){
for(int y = 0; y < 4; y++){
array[x][y]++;
}
}
tile ( 2 , 2 )
(0,0) (0,1) (0,3)(0,2)
(1,0) (1,1) (1,3)(1,2)
(2,0) (2,1) (2,3)(2,2)
(3,0) (3,1) (3,3)(3,2)
- 35. TILE クローズ
#pragma acc data copy(A[:n*m]) copyin(Anew[:n*m])
while ( err > tol && iter < iter_max ) {
err=0.0;
#pragma acc parallel loop reduction(max:err) tile(32,32)
copyin(A[0:n*m]) copy(Anew[0:n*m])
for( int j = 1; j < n-1; j++) {
for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] +
A[j-1][i] + A[j+1][i]);
err = max(err, abs(Anew[j][i] - A[j][i]));
}
}
#pragma acc parallel loop tile(32,32)
copyin(Anew[0:n*m]) copyout(A[0:n*m])
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
A[j][i] = Anew[j][i];
}
}
iter++;
}
データ局所性を有効に使うた
めに、32x32 の tiles を⽣成
- 36. TILING の結果 (V100)
Tile クローズは、オプションのしらみつぶしの探
索が必要になる。
ここでのサンプルコードでは…
• CPU では、 tiling による性能向上は⾒ら
れなかった。
• GPU では、パラメータによって 23% の性能
低下から、10% の性能向上が⾒られた。
CPU
Improvement
GPU
Improvement
Baseline 1.00X 1.00X
4x4 1.00X 0.77X
4x8 1.00X 0.92X
8x4 1.00X 0.95X
8x8 1.00X 1.02X
8x16 1.00X 0.99X
16x8 1.00X 1.02X
16x16 1.00X 1.01X
16x32 1.00X 1.06X
32x16 1.00X 1.07X
32x32 1.00X 1.10X
- 38. GANG, WORKER, VECTOR クローズ
§ 開発者はクローズによって、ループに対する並
列性のレベルをコンパイラに指⽰することがで
きる︓
§ gang – gang レベルの並列化
§ worker – worker レベルの並列化
§ vector – vector レベルの並列化
これらを組み合わせて使うことも可能。
#pragma acc parallel loop gang
for( i = 0; i < size; i++ )
#pragma acc loop worker
for( j = 0; j < size; j++ )
#pragma acc loop vector
for( k = 0; k < size; k++ )
c[i][j] += a[i][k] * b[k][j];
#pragma acc parallel loop
collapse(3) gang vector
for( i = 0; i < size; i++ )
for( j = 0; j < size; j++ )
for( k = 0; k < size; k++ )
c[i][j] += a[i][k] * b[k][j];
- 39. SEQ クローズ
§ Seq (sequential の略) クローズは、そのルー
プを逐次的に実⾏するようコンパイラに指⽰
する。
§ 右のサンプル コードでは、外側2つのループは
並列化されるが、最も内側のループは逐次
的に実⾏される。
§ コンパイラが⾃動で seq クローズを適⽤する
場合もある。
#pragma acc parallel loop
for( i = 0; i < size; i++ )
#pragma acc loop
for( j = 0; j < size; j++ )
#pragma acc loop seq
for( k = 0; k < size; k++ )
c[i][j] += a[i][k] * b[k][j];
- 40. GANGS, WORKERS, VECTORS の調整
#pragma acc parallel num_gangs(2)
num_workers(2) vector_length(32)
{
#pragma acc loop gang worker
for(int x = 0; x < 4; x++){
#pragma acc loop vector
for(int y = 0; y < 32; y++){
array[x][y]++;
}
}
}
基本的にはコンパイラが⾃動で gang, worker の数、
vector ⻑さを選択するが、クローズを⽤いることで、
それらを指定することもできる。
§ num_gangs(N) – N個の gang を⽣成
§ num_workers(M) – M個の worker を⽣成
§ vector_length(Q) – vector ⻑を Q に設定
- 41. COLLAPSE クローズと VECTOR ⻑指定の組み合わせ
#pragma acc data copy(A[:n*m]) copyin(Anew[:n*m])
while ( err > tol && iter < iter_max ) {
err=0.0;
#pragma acc parallel loop reduction(max:err) collapse(2) vector_length(1024)
copyin(A[0:n*m]) copy(Anew[0:n*m])
for( int j = 1; j < n-1; j++) {
for(int i = 1; i < m-1; i++) {
Anew[j][i] = 0.25 * (A[j][i+1] + A[j][i-1] +
A[j-1][i] + A[j+1][i]);
err = max(err, abs(Anew[j][i] - A[j][i]));
}
}
#pragma acc parallel loop collapse(2) vector_length(1024)
copyin(Anew[0:n*m]) copyout(A[0:n*m])
for( int j = 1; j < n-1; j++) {
for( int i = 1; i < m-1; i++ ) {
A[j][i] = Anew[j][i];
}
}
iter++;
}
- 43. LOOP 最適化の⼤まかな指針(経験則)
§ 開発者が gang の数を指定せずに、コンパイラに任せた⽅が良い場合が多い。
§ ほとんどの場合、vector ⻑をチューニングするだけで、効果的にループを最適化することができる。
§ Worker ループを使うことはまれ。Vector ⻑が極端に短い場合は、worker ループによって gang
の中の並列性を向上させることができる。
§ 可能であれば、vector ループは配列に連続にアクセスするのが望ましい。
§ Gang は外側のループから、vector は内側のループから⽣成する。
- 45. キー コンセプト
本講義で学んだこと…
§ GPU プロファイルから最適化のための様々な情報が⼊⼿可能であること。
§ はじめての、Gangs, Workers, and Vectors。
§ Collapse クローズ。
§ Tile クローズ。
§ Gang/Worker/Vector クローズ。