numa-programming について
マルチソケットサーバーや高性能ソフトウェアを扱う開発者向けの NUMA プログラミングスキルです。トポロジー検出、メモリとスレッドの配置、データ構造、リモートアクセス診断を解説します。
主な機能
- 01NUMA トポロジーとノード距離の確認
- 02プロセス、スレッド、メモリのバインド
- 03libnuma によるメモリ割り当てと移動
- 04ノード単位のデータ構造設計
- 05perf によるリモートアクセス診断
- 06lstopo によるハードウェア構成の可視化
活用シーン
- 01マルチソケット環境のスケーリング不良の調査
- 02データベースやキャッシュのメモリ局所性向上
- 03シャーデッドアロケーター向けノード別プール設計
- 04ローカルとリモートのメモリアクセス比較
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mohitmishra786/ low-level-dev-skills --skill numa-programmingインストール後、Claude Code で/numa-programmingを入力して読み込まれたか確認します。
詳細
NUMA プログラミング
目的
NUMA を考慮したプログラミングについてエージェントをガイドします。numactl と sysfs によるトポロジー検出、libnuma API(numa_alloc_onnode、mbind、set_mempolicy)、numactl によるプロセスのバインド、NUMA を考慮したデータ構造、perf stat によるリモートアクセスの診断、lstopo による可視化を扱います。
使用する場面
- マルチソケットサーバーで、CPU 使用率が低いにもかかわらずスケーリングが不十分な場合
- 1 つの NUMA ノードでメモリ帯域幅が飽和している場合
- データベースやキャッシュのプロセスをローカルメモリにバインドする場合
- ノードごとのフリーリストやシャーディングされたアロケーターを設計する場合
- リモートメモリアクセスとローカルメモリアクセスのレイテンシーを測定する場合
- HPC、DPDK、またはカスタムアロケーターをソケット局所性に合わせてチューニングする場合
ワークフロー
1. トポロジーの検出
# Hardware topology summary
numactl --hardware
# Detailed topology with distances
lstopo --of console
# sysfs nodes
ls /sys/devices/system/node/
cat /sys/devices/system/node/node0/meminfo
cat /sys/devices/system/node/node0/cpulist
典型的な出力:
available: 2 nodes (0-1)
node 0 cpus: 0-15
node 0 size: 65536 MB
node 1 cpus: 16-31
node 1 size: 65536 MB
node distances:
node 0 1
0: 10 21
1: 21 10
距離 10 はローカル、それより大きい値はリモート(ソケット間)です。
2. numactl によるプロセスのバインド
# Bind to node 0 CPUs and memory
numactl --cpunodebind=0 --membind=0 ./myapp
# Interleave memory across all nodes
numactl --interleave=all ./myapp
# Preferred node (fallback if full)
numactl --preferred=0 ./myapp
# Show process NUMA policy
numactl --show
cat /proc/self/numa_maps
3. libnuma API
#include <numa.h>
#include <numaif.h>
#include <stdio.h>
int main(void) {
if (numa_available() < 0) {
fprintf(stderr, "NUMA not available\n");
return 1;
}
int node = numa_node_of_cpu(0);
printf("CPU 0 on node %d\n", node);
// Allocate on specific node
size_t size = 1024 * 1024 * 1024;
void *mem = numa_alloc_onnode(size, 0);
if (!mem) return 1;
// Bind existing memory
unsigned long nodemask = 1UL << 0;
mbind(mem, size, MPOL_BIND, &nodemask, sizeof(nodemask) * 8, 0);
numa_free(mem, size);
return 0;
}
gcc -o numa_test numa_test.c -lnuma
| API | 目的 |
|---|---|
numa_alloc_onnode | 特定のノードに割り当てる |
numa_alloc_local | 現在の CPU が属するノードに割り当てる |
mbind | 既存のマッピングにポリシーを設定する |
set_mempolicy | 以降の割り当てに対するデフォルトポリシーを設定する |
move_pages | ページを対象ノードに移行する |
4. NUMA を考慮したデータ構造
// Per-node freelist — avoids cross-node synchronization
#define MAX_NODES 8
struct per_node_pool {
void *free_list[MAX_NODES];
int node_count;
};
void *pool_alloc_numa(struct per_node_pool *p) {
int node = numa_node_of_cpu(sched_getcpu());
void *blk = p->free_list[node];
if (blk) {
p->free_list[node] = *(void **)blk;
return blk;
}
return numa_alloc_onnode(BLOCK_SIZE, node);
}
スレッドを、そのプールと同じノード上のコアに固定します。
5. スレッドアフィニティーの調整
# Pin thread 0 to CPU 0 (node 0), allocate on node 0
numactl --cpunodebind=0 --membind=0 ./worker --id 0
numactl --cpunodebind=1 --membind=1 ./worker --id 1
#include <pthread.h>
#include <sched.h>
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(target_cpu, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);
6. リモートアクセスの診断
# Cache misses often spike with remote memory
perf stat -e cache-misses,cache-references,node-load-misses \
numactl --cpunodebind=0 --membind=1 ./myapp
# Compare local vs remote binding
perf stat numactl --cpunodebind=0 --membind=0 ./myapp
perf stat numactl --cpunodebind=0 --membind=1 ./myapp
# NUMA hit/miss stats (if available)
perf stat -e node-loads,node-load-misses,node-stores ./myapp
7. リモートアクセスのペナルティー測定
// Microbenchmark: touch 1GB on local vs remote node
clock_t start = clock();
for (size_t i = 0; i < size; i += 4096)
sum += ((char *)mem)[i];
インターコネクト(QPI/UPI/Infinity Fabric)に応じて、リモートアクセスでは 1.5~3 倍の速度低下が見込まれます。
8. lstopo による可視化
# Graphical (if X11)
lstopo
# Text with memory/PCI
lstopo --of ascii
# Export for documentation
lstopo file.png
NUMA ノード、コア、キャッシュ、PCI デバイスを表示します。DPDK の NIC 配置には不可欠です。
9. 判断フロー
Poor scaling on multi-socket?
├── Check numactl --hardware
├── Verify thread and memory on same node
├── perf stat node-load-misses
├── Remote misses high?
│ ├── numactl --membind=local
│ └── Per-node data partitioning
└── Still slow → memory bandwidth bound; reduce sharing
よくある問題
| 症状 | 原因 | 対処法 |
|---|---|---|
| 他の場所に空き RAM があるのに、1 つのノードで OOM が発生する | MPOL_BIND の制約が厳しすぎる | --preferred を使うか、インターリーブする |
| スレッド数を増やした後に 2 倍遅くなる | リモートメモリアクセス | CPU ノードに合わせて numactl --membind を指定する |
| ベンチマーク結果にばらつきがある | OS がページを移行した | mbind で MPOL_BIND を指定する。必要に応じて mlock を使う |
| DPDK NIC が誤ったソケットにある | PCI デバイスが CPU から遠い | lstopo を使い、EAL をローカルソケットにバインドする |
| libnuma が見つからない | パッケージがインストールされていない | apt install libnuma-dev |
| First-touch ポリシーによる想定外の動作 | ノード 0 で割り当て、ノード 1 で実行している | バインドしたスレッドから割り当てる |
関連スキル
skills/allocators/custom-allocators— ノードごとのプールアロケーターskills/async-io/dpdk—rte_malloc_socket、NIC の NUMA 局所性skills/hpc/mpi— NUMA ノードごとの MPI プロセスのバインドskills/profilers/hardware-counters— キャッシュミスの測定skills/profilers/linux-perf— perf の NUMA イベントskills/low-level-programming/cpu-cache-opt— キャッシュ局所性の基礎
コメント
Markdown 対応