NUMA環境で,スレッドやメモリの配置を明示的に指示する方法を調べたので,メモしておく. 1つの筐体に複数のマルチコアCPUを搭載する環境では,メモリはCPUごとに接続される. このような環境では, バスを介して直接つながったメモリ(ローカルメモリ)とCPUの間で,高速にデータを転送できる. 一方,直接つながっていないメモリ(リモートメモリ)にデータを転送するためには, QPI(QuickPath Interconnect)などのインタコネクトを介して, 他のCPUソケットを経由する必要がある. このようにメモリアクセスの仕組みが複数存在する環境は, NUMA(Nun Uniform Memory Access)と呼ばれる.
NUMA環境で,マルチスレッドプログラムをチューイングするためには, スレッドをどのコアに割り当てるか,データをどのメモリに配置するかが重要になる. これらの制御は,numactlコマンドあるいはlibnumaライブラリによって実現できる.
numactlによる制御
ソースコードを修正できない状況(あるいは面倒くさい状況)では,
numactlコマンドを使って,アフィニティを設定する.
--cpubind=<nodemask>
オプションでスレッドをどのノードで実行するか指示し,
--membind=<nodemask>
オプションでデータをどのノードのメモリに配置するかを指示する.
<nodemask>
には,--membind=0,1
のようにノード番号をカンマ区切りで記述する.
他にも,
優先してメモリを割り当てるノードを指示する--preferred=<nodenumber>
オプションや
複数のノードにインタリーブでメモリを割り当てる--interleave=<nodemask>
オプションがある.
ノード番号などハードウェア情報は,--hardware
オプションで確認できる.
また,プロセスに割り当てられたポリシーを確認するには,--show
オプションを使う.
# スレッドをノード0,データをノード0および1に配置
$ numactl --cpubind=0 --membind=0,1 ./a.out
# メモリをインタリーブに配置し,numactl --showで確認
$ numactl --interleave=all numactl --show
libnumaによる制御
ソースコードを修正できる状況では,libnumaを使う.
numactlはプログラム全体のメモリ割付けを制御するが,
libnumaは個々のメモリ領域を個別に制御する.
libnumaを利用するには,コードにnuma.h
をヘッダを追加し,
共有ライブラリをリンクする(-lnuma
).
ノード番号の集合<nodemask>
は,nodemask_t
型変数に格納する.
メモリの確保では,どのようにメモリを確保するのかに従って,
適切なnuma_alloc_*
ファミリの関数を利用する.
メモリの解放には,共通してnuma_free
関数を利用する.
さらに,numa_run_on_node
あるいはnuma_run_on_node_mask
関数を使うことで,
スレッドをどのノードで実行するかを明示できる.
nodemask_t m; // ノード番号の集合を格納する変数m
nodemask_zero(&m); // mを初期化
nodemask_set(&m, 2); // ノード番号2を有効に
nodemask_clr(&m, 2); // ノード番号2を無効に
nodemask_all_nodes(&m); // すべてのノードを有効に
nodemask_no_nodes(&m); // 空集合に
nodemask_isset(&m, 2); // ノード番号2がセットされていれば真
size_t s = 4 * 1024; // データサイズ4KB
// 2番目のノードに確保
void *mem1 = numa_alloc_onnode(s, 2);
// すべてのノードにインタリーブに確保
void *mem2 = numa_alloc_interleaaved(s);
// mで示されたノードにインタリーブに確保
void *mem3 = numa_alloc_interleaaved_subset(s, m);
// ローカルメモリに確保
void *mem4 = numa_alloc_local(s);
// メモリ解放
numa_free(mem1,s); numa_free(mem2,s); numa_free(mem3,s); numa_free(mem4,s);
// 現在のスレッドをノード1で実行
numa_run_on_node(1);
// 現在のスレッドをmに含まれるどこかのノードで実行
nodemask_zero(&m);
nodemask_set(&m,1);
nodemask_set(&m,2);
numa_run_on_node_mask(m);
OpenMPによる制御
OpenMPを使ってマルチスレッドを実現している場合には,環境変数によってアフィニティを制御する.
制御方法は,コンパイラごとに異なり,例えば,
PGIコンパイラであればMP_BIND
およびMP_BLIST
を設定する.