Jump to content

NUMA

From Haidhi Wiki
Revision as of 07:41, 3 July 2026 by SanzSkuy (talk | contribs) (Created page with "'''NUMA''' ('''Non-Uniform Memory Access''') adalah arsitektur memori di mana '''waktu akses ke RAM tidak seragam''': tergantung memorinya "milik" siapa. Setiap grup CPU (disebut '''NUMA node''') punya RAM lokalnya sendiri yang cepat diakses; mengakses RAM milik node lain tetap bisa, tapi harus lewat interkoneksi antar-node sehingga lebih lambat. NUMA adalah kenyataan sehari-hari di server multi-socket dan CPU chiplet modern (AMD EPYC, Xeon besar). Program yang mengangg...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

NUMA (Non-Uniform Memory Access) adalah arsitektur memori di mana waktu akses ke RAM tidak seragam: tergantung memorinya "milik" siapa. Setiap grup CPU (disebut NUMA node) punya RAM lokalnya sendiri yang cepat diakses; mengakses RAM milik node lain tetap bisa, tapi harus lewat interkoneksi antar-node sehingga lebih lambat.

NUMA adalah kenyataan sehari-hari di server multi-socket dan CPU chiplet modern (AMD EPYC, Xeon besar). Program yang menganggap "semua RAM sama saja" bisa kehilangan performa signifikan di mesin NUMA — bukan karena hitungannya lambat, tapi karena datanya "tinggal di rumah tetangga". Bersama Cache line dan layout data, ini adalah fondasi ketiga dari optimasi memori di HPC.

Analogi Sederhana

Bayangkan kantor dengan dua gedung. Tiap gedung punya tim sendiri dan lemari arsipnya sendiri.

  • Tim A butuh dokumen yang ada di lemari gedungnya sendiri → tinggal jalan beberapa langkah. Cepat. Ini akses lokal.
  • Tim A butuh dokumen yang disimpan di gedung sebelah → harus nyeberang lewat jembatan penghubung, dan jembatan itu dipakai bareng-bareng. Lebih lama, dan kalau ramai bisa antre. Ini akses remote.

Isi dokumennya sama saja — yang beda cuma jaraknya. Kunci performa di NUMA: usahakan tiap tim bekerja dengan arsip yang disimpan di gedungnya sendiri.

UMA vs NUMA

Dulu, sistem multi-prosesor memakai model UMA (Uniform Memory Access / SMP): semua CPU berbagi satu bus ke satu memori.

UMA / SMP (model lama):

  +-------+   +-------+   +-------+   +-------+
  | CPU 0 |   | CPU 1 |   | CPU 2 |   | CPU 3 |
  +---+---+   +---+---+   +---+---+   +---+---+
      |           |           |           |
  ====+===========+=====+=====+===========+====   <- SATU bus bersama
                        |
                  +-----+-----+
                  |    RAM    |
                  +-----------+

  Masalah: makin banyak CPU, bus jadi rebutan.
  Tidak bisa di-scale.

Solusinya: pindahkan memory controller ke dalam tiap CPU, beri tiap CPU RAM-nya sendiri, lalu sambungkan antar-CPU dengan interkoneksi cepat (Intel: UPI, AMD: Infinity Fabric). Lahirlah NUMA:

NUMA (2 node):

        NODE 0                            NODE 1
+---------------------+          +---------------------+
|      Socket 0       |          |      Socket 1       |
|  core core core ... |          |  core core core ... |
|  [memory controller]|<========>|  [memory controller]|
+----------+----------+   UPI /  +----------+----------+
           |             Infinity           |
     +-----+-----+        Fabric      +-----+-----+
     | RAM lokal |                    | RAM lokal |
     |  node 0   |                    |  node 1   |
     +-----------+                    +-----------+

Akses LOKAL  : core node 0 -> RAM node 0   (cepat, ~80-100 ns)
Akses REMOTE : core node 0 -> RAM node 1   (lewat interkoneksi,
                                            ~1.5-2x lebih lambat,
                                            bandwidth lebih kecil)

Rasio "berapa kali lebih lambat akses remote dibanding lokal" disebut NUMA factor, umumnya sekitar 1,5–2x untuk 2 socket, dan bisa lebih besar di sistem 4–8 socket.

Catatan penting era sekarang: NUMA tidak selalu berarti multi-socket. CPU chiplet seperti AMD EPYC bisa dipecah jadi beberapa NUMA node di dalam satu socket (setting BIOS NPS1/NPS2/NPS4), dan Intel punya Sub-NUMA Clustering (SNC). Laptop/desktop biasa umumnya cuma 1 node — jadi masalah ini baru terasa begitu kode pindah ke server.

Melihat Topologi NUMA

Perintah paling penting: numactl --hardware.

$ numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
node 0 size: 64220 MB
node 0 free: 58109 MB
node 1 cpus: 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
node 1 size: 64485 MB
node 1 free: 61022 MB
node distances:
node   0   1
  0:  10  21
  1:  21  10

Cara membaca matriks distance: angka relatif, 10 = akses lokal. Nilai 21 artinya akses silang kira-kira 2,1x "lebih jauh" dari lokal. Alternatif lain:

lscpu | grep -i numa      # ringkasan node & CPU-nya
lstopo                    # gambar topologi lengkap (paket hwloc)
cat /sys/devices/system/node/node0/cpulist
numastat                  # statistik hit/miss alokasi per node

numa_hit vs numa_foreign/other_node di numastat adalah indikator cepat apakah programmu banyak makan akses remote.

First-Touch: Jebakan Paling Umum

Di Linux, malloc/new belum menentukan halaman memori ditaruh di node mana. Kebijakan default-nya first-touch: halaman (page) fisik baru dialokasikan di node milik thread yang pertama kali menyentuhnya (menulisnya) — bukan yang meng-alokasikan.

Konsekuensinya di kode OpenMP, ini jebakan klasik:

double *a = new double[N];        // belum ada page fisik

// JEBAKAN: inisialisasi serial oleh thread utama (node 0)
for (long i = 0; i < N; ++i)
    a[i] = 0.0;                   // SEMUA page first-touch di node 0!

#pragma omp parallel for schedule(static)
for (long i = 0; i < N; ++i)
    a[i] = kerja_berat(a[i]);     // thread di node 1 akses remote terus
Akibat init serial:

RAM node 0 : [pg][pg][pg][pg][pg][pg][pg][pg]   <- semua di sini
RAM node 1 : (kosong)

Saat fase paralel:
  thread node 0 --> lokal   (cepat)
  thread node 1 --> remote SEMUA, lewat interkoneksi (lambat),
                    plus memory controller node 0 jadi rebutan
                    oleh 2 node sekaligus (bandwidth mentok separuh)

Perbaikannya sederhana: inisialisasi juga secara paralel, dengan pembagian kerja yang sama dengan loop komputasinya — supaya tiap thread "menyentuh pertama" bagian data yang memang akan ia pakai:

// BENAR: first-touch paralel, pembagian identik (schedule(static))
#pragma omp parallel for schedule(static)
for (long i = 0; i < N; ++i)
    a[i] = 0.0;                   // page tersebar mengikuti thread

#pragma omp parallel for schedule(static)
for (long i = 0; i < N; ++i)
    a[i] = kerja_berat(a[i]);     // mayoritas akses jadi lokal

Supaya trik ini bekerja, thread tidak boleh pindah-pindah core antar node — makanya first-touch selalu dipasangkan dengan pinning (bagian berikut).

Pinning / Affinity

Secara default OS bebas memindahkan thread antar core, termasuk nyeberang node — yang merusak lokalitas yang sudah susah payah diatur. Kunci thread/proses ke tempatnya:

OpenMP:

export OMP_PROC_BIND=close        # atau spread
export OMP_PLACES=cores
./program

numactl (per proses):

numactl --cpunodebind=0 --membind=0 ./program   # kunci CPU + RAM ke node 0
numactl --interleave=all ./program              # sebar page round-robin ke semua node
numastat -p $(pidof program)                    # audit: page-nya beneran di mana?

MPI (OpenMPI): pola yang umum di cluster adalah satu rank per NUMA node, lalu OpenMP mengisi core di dalam node itu (hybrid MPI+OpenMP):

mpirun --map-by numa --bind-to numa -np 2 ./program
# tiap rank "terkurung" di satu node -> semua alokasinya otomatis lokal

Ini salah satu alasan desain hybrid populer: MPI memaksa pemisahan memori antar rank, sehingga masalah NUMA hilang dengan sendirinya di level antar-node.

Kebijakan Alokasi Memori

Kebijakan Perilaku Cocok untuk
default (first-touch) Page ditaruh di node thread yang pertama menyentuh Kode yang init-nya sudah paralel & thread di-pin
bind (--membind) Page dipaksa di node tertentu; gagal kalau penuh Proses yang memang dikurung di satu node
preferred Coba node tertentu dulu, boleh meluber ke node lain Mirip bind tapi tidak mau gagal alokasi
interleave (--interleave) Page disebar bergilir ke semua node Data besar yang diakses acak oleh semua thread (bandwidth dibagi rata, latency rata-rata)

Untuk kontrol dari dalam kode, ada libnuma (numa_alloc_onnode(), mbind(), move_pages()).

Kernel Linux juga punya Automatic NUMA Balancing (/proc/sys/kernel/numa_balancing) yang diam-diam memigrasi page mendekati thread pemakainya. Kadang membantu, tapi untuk benchmark HPC yang sudah diatur manual biasanya justru dimatikan supaya hasilnya stabil.

Gejala Khas Masalah NUMA

  • Program scaling bagus sampai jumlah thread = 1 node, lalu mendadak jelek begitu meluber ke node kedua.
  • Waktu eksekusi tidak stabil antar-run (kadang cepat kadang lambat, tergantung OS menaruh thread di mana).
  • numastat menunjukkan other_node/numa_foreign tinggi.
  • Bandwidth memori terukur (mis. benchmark STREAM) jauh di bawah spesifikasi total — karena semua traffic menumpuk di satu memory controller.
  • Di perf: event node-load-misses / remote access tinggi.

Cara uji cepat: jalankan program dengan numactl --cpunodebind=0 --membind=0 (semuanya lokal). Kalau versi "dikurung satu node" ini lebih cepat daripada versi bebas pakai semua core — hampir pasti ada masalah penempatan data.

Tips Praktis

  • Kenali dulu mesinmu: numactl --hardware dan lstopo sebelum tuning apa pun. Di laptop 1 node, semua ini tidak relevan.
  • Init paralel + pinning adalah kombinasi wajib: first-touch hanya berguna kalau thread tidak berpindah node.
  • Data mengikuti komputasi: partisi data per thread/rank konsisten dari awal sampai akhir program.
  • Untuk akses yang benar-benar acak dan dibagi semua thread, --interleave=all sering jadi kompromi terbaik.
  • Hybrid MPI+OpenMP: satu rank per NUMA node adalah titik awal yang hampir selalu masuk akal.
  • Audit, jangan asumsi: numastat -p untuk melihat page beneran di node mana, perf untuk menghitung akses remote.
  • Ingat urutan prioritas optimasi memori: algoritma → pola akses & cache → layout data → NUMA. Tuning NUMA di atas pola akses yang berantakan itu percuma.

Lihat Juga

Referensi

Kategori:HPC Kategori:Arsitektur Komputer Kategori:Optimasi Performa