4. University of Tokyo
4/66
Memory Memory Memory
Cache
CPU
Cache
CPU
Cache
CPU
Network
プログラム階層
ノード間通信
(並列化)
メモリ転送
(キャッシュ最適化)
CPUコア
(チューニング)
SIMD化、if分岐削除
この中で最も重要なのはメモリ転送
7. University of Tokyo
7/66
仮想メモリとページング (1/2)
仮想メモリとは
物理メモリと論理メモリをわけ、ページという単位で管理するメモリ管理方法
仮想メモリを使う理由
・不連続なメモリ空間を論理的には連続に見せることができる
仮想メモリなしでは、メモリに余裕があっても連続領域が取れない場合にメモリ割り当てができない
・スワッピングが可能になる
記憶容量としてハードディスクも使えるため、物理メモリより広い論理 メモリ空間が取れる。
ハードディスク
仮想メモリ
(論理メモリ)
物理メモリ
連続配列が、物理メモリ内
では不連続に割り当てら
れてるかもしれない
一部がハードディスクにスワップさ
れているかもしれない
論理メモリと実メモリの対応が書いてあるのがTLB (Translation Lookaside Buffer)
ページサイズを大きく取るのがラージページ
8. University of Tokyo
8/66
数値計算で何が問題になるか?
F90のallocateや、Cでnew、mallocされた時点では物理メモリの
割り当てがされていない場合がある
real*8, allocatable :: work(:)
allocate (work(10000))
do i=1, 10000
work(i) = i
end do
この時点では予約だけされて、まだ物理アドレスが割り当てられない
はじめて触った時点で、アドレスがページ単位で割り当てられる
(First touch の原則)
よくある問題:
最初に馬鹿でかい配列を動的に確保しているプログラムの初期化がすごく遅い
地球シミュレータから別の計算機への移植で問題になることが多い
OSがLinuxである京でも発生する
解決策:
メモリを確保した直後、ページ単位で全体を触っておく
メモリを静的に確保する(?)
まぁ、そういうこともあると覚えておいてください
仮想メモリとページング (2/2)
9. University of Tokyo
9/66
NUMA (1/2)
NUMAとは
非対称メモリアクセス(Non-Uniform Memory Access)の略
CPUにつながっている物理メモリに近いメモリと遠いメモリの区別がある
京コンピュータはNUMAではないが、物性研System BはNUMA
CPU
CPU
QPI
Memory
Memory
Memory
Memory
Memory
Memory
Fast access
Slow access
特にlatency
10. University of Tokyo
10/66
数値計算で何が問題になるか?
NUMA (2/2)
OpenMPでCPUをまたぐ並列化をする際、初期化を適当にやると
作業領域がコアから遠い物理メモリに割り当てられてしまう
QPI
CORE
CORE
CORE
CORE
CORE
CORE
CORE
CORE
OpenMPのスレッドを作る前に配列を初期化
→root プロセスの近くにメモリ割り当て
その後OpenMPでスレッド並列
→遠いメモリをアクセス(遅い)
Touch
解決策:
スレッドに対応する物理コアに近いところにメモリが割り当てられるようにする
→ スレッドごとに配列を用意した上、OpenMPでスレッド並列化した後にtouch
詳しくはNUMA最適化でググってください
12. University of Tokyo
12/66
ゲーム機とCPU
第六世代
DC SH-4
PS2 MIPS (Emotion Engine)
GC IBM PowerPC カスタム (Gekko)
Xbox Intel Celeron (PenIII ベース)
第七世代
Wii IBM PowerPC カスタム
Xbox 360 IBM PowerPC カスタム
PS3 IBM Cell 3.2
第八世代・・・?
CPUアーキテクチャ (2/2)
24. University of Tokyo
24/66
gprof
gprofとは
広く使われるプロファイラ(sampler)
(Macでは使えないようです)
$ gcc -pg test.cc
$ ./a.out
$ ls
a.out gmon.out test.cc
$ gprof a.out gmon.out
Flat profile:
Each sample counts as 0.01 seconds.
% cumulative self self total
time seconds seconds calls ms/call ms/call name
100.57 0.93 0.93 1 925.26 925.26 matmat()
0.00 0.93 0.00 1 0.00 0.00 global constructors keyed to A
0.00 0.93 0.00 1 0.00 0.00 __static_initialization_and_destruction_0(int, int)
0.00 0.93 0.00 1 0.00 0.00 init()
0.00 0.93 0.00 1 0.00 0.00 matvec()
0.00 0.93 0.00 1 0.00 0.00 vecvec()
使い方
出力
とりあえずEach % timeだけ見ればいいです
サンプリングレートも少しだけ気にすること
25. University of Tokyo
25/66
結果の解釈 (Sampler型)
一部のルーチンが80%以上の計算時間を占めている
→そのルーチンがホットスポットなので、高速化を試みる
多数のルーチンが計算時間をほぼ均等に使っている
→最適化をあきらめる
世の中あきらめも肝心です
あきらめたらそこで試合終了じゃ
ないんですか?
※最適化は、常に費用対効果を考えること
26. University of Tokyo
26/66
プロファイラ(イベント取得型)
Hardware Counter
CPUがイベントをカウントする機能を持っている時に使える(京など)
イベントをサンプリングするプロファイラもある (Intel VTuneなど)
プロファイラの使い方
システム依存
京では、カウントするイベントの種類を指定
プログラムの再コンパイルは不必要
カウンタにより取れるイベントが決まっている
→同じカウンタに割り当てられたイベントが知りたい場合、複数回実行する必要がある
←こっちに気を取られがちだが
←個人的にはこっちが重要だと思う
取得可能な主なイベント:
・整数演算
・浮動小数点演算
・キャッシュミス
・バリア待ち
・演算待ち
27. University of Tokyo
27/66
Profile結果の解釈 (HW Counter)
バリア同期待ち
OpenMPのスレッド間のロードインバランスが原因
自動並列化を使ったりするとよく発生
対処:自分でOpenMPを使ってちゃんと考えて書く(それができれば苦労はしないが)
キャッシュ待ち
浮動小数点キャッシュ待ち
対処:メモリ配置の最適化 (ブロック化、連続アクセス、パディング・・・)
ただし、本質的に演算が軽い時には対処が難しい
演算待ち
浮動小数点演算待ち
A=B+C
D=A*E ←この演算は、前の演算が終わらないと実行できない
対処:アルゴリズムの見直し (それができれば略)
SIMD化率が低い
対処できる場合もあるが、あきらめた方がはやいと思う
それでも対処したい人へ:
ループカウンタ間の依存関係を減らしてsoftware pipeliningを促進
43. University of Tokyo
43/66
1. foreach interacting particles
2. r ← particle distance
3. if distance > cutoff length then continue
4. f ← calculate force
5. p ← update momenta
6. next
ここが重い
1. foreach interacting particles
2. r ← particle distance
3. f ← calculate force
4. if distance > cutoff length then f ←0
5. p ← update momenta
6. next
fsel 命令が使われる
余計な計算量が増えるが、パイプラインがスムーズに流れる
ために計算が早くなる(こともある)
条件分岐削除 (1/2)
(次のループが回るかわからないから)
(全てのループが確実にまわる)
44. University of Tokyo
44/66
経過時間[s]
fsel
fp0,fp1,fp2,fp3
fp0
=
(fp1
>
0)?
fp2:
fp3;
条件分岐削除 (2/2)
IBM POWER の実行が大幅に高速化された
IBM POWERや、京には条件代入命令(fsel)がある
これにより、マスク処理が可能
条件分岐なし
条件分岐あり
条件分岐削除
45. University of Tokyo
45/66
SIMD化 (1/4)
SIMD化とは何か
コンパイラが出力する命令のうち、SIMD命令の割合を増やす事
スカラ命令: 一度にひとつの演算をする
SIMD命令(ベクトル命令):複数の対象にたいして、同種の演算を一度に行う
実際にやること
コンパイラがSIMD命令を出しやすいようにループを変形する
SIMD命令を出しやすいループ
=演算に依存関係が少ないループ
コンパイラがどうしてもSIMD命令を出せなかったら?
手作業によるSIMD化
(Hand-SIMDize)
ほとんどアセン
ブリで書くよう
なものです
46. University of Tokyo
46/66
ループアンローリング
DO
I
=
1,
N
C[i]
=
A[i]
+
B[i]
E[i]
=
C[i]
+
D[i]
G[i]
=
E[i]
+
F[i]
I[i]
=
G[i]
+
H[i]
END
DO
依存関係があるのでSIMD命令が発行されない。
A[1]+B[1]
C[1]+D[1]
E[1]+F[1]
ループを二倍展開し、独立な計算を作る (馬鹿SIMD化)
問題点:
・レイテンシを隠しづらい
・積和が作りにくい
→ 遅い
京やFX10で
Loop Unrolled x times
とメッセージが出たらこれ
I[1]+G[1]
A[2]+B[2]
C[2]+D[2]
E[2]+F[2]
I[2]+G[2]
SIMD化 (2/4)
47. University of Tokyo
47/66
ソフトウェアパイプライニング
DO I = 1, N
C[i] = A[i] + B[i]
E[i] = C[i] + D[i]
H[i] = F[i] + G[i]
H[i] = H[i] + I[i]
END DO
独立な計算を増やしやすい
レイテンシを隠し易い
人間の手でやるのはかなり厳しい
だが、京、FX10ではこれをやらないと速度が出ない
→いかにコンパイラに「Loop software pipelined」を出させるか
A[1]+B[1]
C[1]+D[1]
E[1]+F[1]
I[1]+G[1]
A[2]+B[2]
C[2]+D[2]
E[2]+F[2]
I[2]+G[2]
A[3]+B[3]
C[3]+D[3]
E[3]+F[3]
I[3]+G[3]
A[4]+B[4]
C[4]+D[4]
E[4]+F[4]
I[4]+G[4]
ループインデックス
SIMD化 (3/4)
48. University of Tokyo
48/66
我々が実際にやったこと
作用反作用を使わない (二倍計算する)
→ 京、FX10ではメモリへの書き戻しがボトルネック
ループを2倍展開した馬鹿SIMDループをさらに二倍アンロール (4倍展開)
→ レイテンシ隠蔽のため、手でソフトウェアパイプライニング
局所一次元配列に座標データをコピー
→ 運動量の書き戻しは行わないので、局所座標の読み出しがネックに
→ 「グローバル、static配列でないとsoftware pipeliningできない」
というコンパイラの仕様に対応するため
除算のSIMD化
→ 京、FX10には除算のSIMD命令がない
→ 逆数近似命令(低精度)+精度補正コードを手で書いた
チューニング成果
力の計算の速度は2倍に、全体でも30%程度の速度向上
SIMD化 (4/4)
56. University of Tokyo
56/66
MPIとOpenMP (1/2)
スレッドとプロセス
スレッド
プロセス
メモリ空間
スレッド
プロセス
メモリ空間
スレッド
スレッド
プロセス
メモリ空間
スレッド
タスク
プロセス:OSから見た利用単位
ひとつ以上のスレッドと、プロセスごとに固有メモリ空間を持つ
スレッド: CPUから見た利用単位
同じプロセス中のスレッドはメモリを共有する
タスク: 人間から見た利用単位
一つ以上のプロセスが協調して一つの仕事を実行する単位
57. University of Tokyo
57/66
Memory Memory Memory
Cache
CPU
Cache
CPU
Cache
CPU
Network
ノード
ノード
ノード
MPIとOpenMP (2/2)
OpenMP
スレッド並列の規格
各並列単位でメモリは共有
MPI
プロセス並列の規格
メモリは独立 (分散)
ノード内は共有メモリ、ノード間は分散メモリ
ノードをまたぐ並列化はMPI必須
60. University of Tokyo
60/66
ハイブリッド並列化(2/5)
一般的方法
MPI: 空間分割
OpenMP: ループ分割
DO I=1,N
DO J in Pair(I)
CalcForce(I,J)
ENDDO
ENDDO
ここか、
ここにスレッド並列をかける
問題点
運動量の書き戻しで同じ粒子にアクセスする可能性がある
→ テンポラリバッファを用意して衝突をさける
→ 作用反作用を使わない
ペアリスト作成もスレッド並列化しなければならない
力の計算というボトルネックで、SIMD化とOpenMP化を同時に扱う必要がある