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を設定する....