Socionext SC0FQAA-BはNUMAか否か
はじめに
本記事はLinuxのプロセススケジューラから見たSocionext SC2A11の続きです。Socionext SC0FQAA-B(以下「本マシン」と記載)は24コアのSC2A11を1つ搭載しています。このマシンがNUMAかどうか、言い方を変えるとSC2A11内の各コアがすべてのメモリに等速でアクセス可能かどうかを調査しました。
結論
本マシンはNUMAではないと推測しました。
linuxからの情報取得
まず、arm64はNUMA構成をサポートしています。LinuxのDevice Treeのドキュメントによると、Device TreeでNUMAを表現するには、各デバイスの定義内のnuma-node-idというトークンによってデバイスが所属するNUMA IDを定義します。
しかし、SC0FQAA-BのDevice Treeにはこのトークンは見つかりませんでした。
$ find /sys/firmware/devicetree -name numa-node-id $
ここで終わりかというとそうではなくて、ハードがNUMAとして見せていないにも関わらず、実測してみると実質NUMAだと明らかになる、というのは珍しい話ではありません。
実験
使用するプログラム
データ採取には次のような仕様のプログラムを使います。
- コア0上でL3キャッシュを超えるサイズのバッファ(ここでは16MB)を取得する
- 以下を24コアのすべてにおいて繰り返す。コア番号をnとする
- 2-1. プロセスをコアnに移動させる
- 2-2. 上記バッファに所定の回数アクセスすることによってメモリアクセス速度を計測
処理1において取得したメモリはプログラムの実行中に物理アドレスが移動することはないため1、処理2-1においてコアを移動した場合にバッファへアクセス速度が変化すれば、おそらく本マシンはNUMAだろう、というわけです。
これを実装したのが次のnuma.cプログラムです。
#define _GNU_SOURCE #include <sched.h> #include <unistd.h> #include <sys/mman.h> #include <time.h> #include <stdio.h> #include <stdlib.h> #include <err.h> #define CACHE_LINE_SIZE 64 #define NLOOP (64*1024*1024) #define BUFFER_SIZE (16*1024*1024) #define NCORE 24 #define NSECS_PER_SEC 1000000000UL static inline long diff_nsec(struct timespec before, struct timespec after) { return ((after.tv_sec * NSECS_PER_SEC + after.tv_nsec) - (before.tv_sec * NSECS_PER_SEC + before.tv_nsec)); } static void setcpu(int n) { cpu_set_t set; CPU_ZERO(&set); CPU_SET(n, &set); if (sched_setaffinity(getpid(), sizeof(cpu_set_t), &set) == -1) err(EXIT_FAILURE, "sched_setaffinity() failed"); } int main(int argc, char *argv[]) { char *progname; progname = argv[0]; register int size = BUFFER_SIZE; setcpu(0); char *buffer; buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (buffer == (void *) -1) err(EXIT_FAILURE, "mmap() failed"); int i; for (i = 0; i < NCORE; i++) { setcpu(i); struct timespec before, after; clock_gettime(CLOCK_MONOTONIC, &before); int j; for (j = 0; j < NLOOP / (size / CACHE_LINE_SIZE); j++) { long k; for (k = 0; k < size; k += CACHE_LINE_SIZE) buffer[k] = 0; } clock_gettime(CLOCK_MONOTONIC, &after); printf("%d\t%f\n", i, (double)diff_nsec(before, after) / NLOOP); } if (munmap(buffer, size) == -1) err(EXIT_FAILURE, "munmap() failed"); exit(EXIT_SUCCESS); }
結果
結果は次の通りです。第一フィールドがデータを採取したコア、第二フィールドがメモリアクセス処理に所要時間です。
0 37.300696 1 37.511301 2 37.320645 3 37.479546 4 37.280271 5 37.298240 6 37.207461 7 37.285339 8 36.824337 9 36.862199 10 36.926653 11 36.867224 12 37.064979 13 37.011814 14 37.041438 15 37.074635 16 37.414566 17 37.373740 18 37.375946 19 37.423050 20 37.292513 21 37.323017 22 37.154486 23 37.190995
次に示すのは、コア0上でバッファにアクセスする測定を24回繰り返した結果です。
36.993787 36.986646 37.254620 37.231557 37.587849 37.582042 37.450369 37.387533 37.487312 37.452216 37.501526 37.556768 37.139728 37.283043 37.169193 37.389888 37.746065 37.841875 37.559928 37.581090 37.769728 37.680080 37.567870 37.194819
いずれも37秒前後1秒程度の差に収まっているため、アクセス速度にあまり違いはないといっていいでしょう。つまり、ある物理メモリ(numa.cにおいて取得したバッファ)には24個いずれのコアからもおおよそ等速でアクセスできたということです。numa.cの処理1においてバッファを採取する際のコアを1-23に変えても結果は同じでした。これによって本マシンは恐らくNUMAではなかろうと推測しました。
-
メモリが断片化してきた場合はコンパクションという機能によって物理メモリが移動することがありますが、今回はほとんどのメモリが空いている状態なのでその心配はありません。↩