<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.haidhi.my.id/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenMP%3A_Affinity_%26_First-Touch</id>
	<title>OpenMP: Affinity &amp; First-Touch - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.haidhi.my.id/wiki/index.php?action=history&amp;feed=atom&amp;title=OpenMP%3A_Affinity_%26_First-Touch"/>
	<link rel="alternate" type="text/html" href="https://wiki.haidhi.my.id/wiki/index.php?title=OpenMP:_Affinity_%26_First-Touch&amp;action=history"/>
	<updated>2026-11-01T19:54:55Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.0</generator>
	<entry>
		<id>https://wiki.haidhi.my.id/wiki/index.php?title=OpenMP:_Affinity_%26_First-Touch&amp;diff=797&amp;oldid=prev</id>
		<title>SanzSkuy: Created page with &quot;Di mesin NUMA, performa program OpenMP ditentukan dua hal yang &#039;&#039;&#039;tidak kelihatan di kode&#039;&#039;&#039;: di core mana tiap thread berjalan (&#039;&#039;&#039;affinity&#039;&#039;&#039;), dan di node memori mana tiap data tinggal (&#039;&#039;&#039;first-touch&#039;&#039;&#039;). Kode yang sama persis bisa beda kecepatan 2× hanya karena dua hal ini tidak selaras. Artikel ini membahas cara mengontrol keduanya — dan kenapa keduanya harus diatur &#039;&#039;bersama-sama&#039;&#039;.  Prasyarat: OpenMP: Parallel For &amp; Scheduling dan konsep NUMA.  = L...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.haidhi.my.id/wiki/index.php?title=OpenMP:_Affinity_%26_First-Touch&amp;diff=797&amp;oldid=prev"/>
		<updated>2026-07-11T06:18:17Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;Di mesin &lt;a href=&quot;/wiki/index.php/NUMA&quot; title=&quot;NUMA&quot;&gt;NUMA&lt;/a&gt;, performa program OpenMP ditentukan dua hal yang &amp;#039;&amp;#039;&amp;#039;tidak kelihatan di kode&amp;#039;&amp;#039;&amp;#039;: di core mana tiap thread berjalan (&amp;#039;&amp;#039;&amp;#039;affinity&amp;#039;&amp;#039;&amp;#039;), dan di node memori mana tiap data tinggal (&amp;#039;&amp;#039;&amp;#039;first-touch&amp;#039;&amp;#039;&amp;#039;). Kode yang sama persis bisa beda kecepatan 2× hanya karena dua hal ini tidak selaras. Artikel ini membahas cara mengontrol keduanya — dan kenapa keduanya harus diatur &amp;#039;&amp;#039;bersama-sama&amp;#039;&amp;#039;.  Prasyarat: &lt;a href=&quot;/wiki/index.php/OpenMP:_Parallel_For_%26_Scheduling&quot; title=&quot;OpenMP: Parallel For &amp;amp; Scheduling&quot;&gt;OpenMP: Parallel For &amp;amp; Scheduling&lt;/a&gt; dan konsep &lt;a href=&quot;/wiki/index.php/NUMA&quot; title=&quot;NUMA&quot;&gt;NUMA&lt;/a&gt;.  = L...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;Di mesin [[NUMA]], performa program OpenMP ditentukan dua hal yang &amp;#039;&amp;#039;&amp;#039;tidak kelihatan di kode&amp;#039;&amp;#039;&amp;#039;: di core mana tiap thread berjalan (&amp;#039;&amp;#039;&amp;#039;affinity&amp;#039;&amp;#039;&amp;#039;), dan di node memori mana tiap data tinggal (&amp;#039;&amp;#039;&amp;#039;first-touch&amp;#039;&amp;#039;&amp;#039;). Kode yang sama persis bisa beda kecepatan 2× hanya karena dua hal ini tidak selaras. Artikel ini membahas cara mengontrol keduanya — dan kenapa keduanya harus diatur &amp;#039;&amp;#039;bersama-sama&amp;#039;&amp;#039;.&lt;br /&gt;
&lt;br /&gt;
Prasyarat: [[OpenMP: Parallel For &amp;amp; Scheduling]] dan konsep [[NUMA]].&lt;br /&gt;
&lt;br /&gt;
= Latar: NUMA dalam Satu Paragraf =&lt;br /&gt;
&lt;br /&gt;
Node cluster modern umumnya punya ≥2 socket CPU, dan tiap socket punya memory controller + RAM sendiri. Semua RAM tergabung dalam satu address space, tapi aksesnya tidak seragam: core di socket 0 mengakses RAM socket 0 (&amp;#039;&amp;#039;&amp;#039;lokal&amp;#039;&amp;#039;&amp;#039;) lebih cepat daripada RAM socket 1 (&amp;#039;&amp;#039;&amp;#039;remote&amp;#039;&amp;#039;&amp;#039;, lewat interconnect antar-socket seperti Intel UPI / AMD Infinity Fabric). Bedanya tipikal 1.5–2× di latensi, dan bandwidth interconnect jauh lebih kecil dari bandwidth memori lokal. Pertanyaannya: siapa yang menentukan sebuah data tinggal di RAM socket mana?&lt;br /&gt;
&lt;br /&gt;
= First-Touch: Memori Dialokasikan Saat Disentuh =&lt;br /&gt;
&lt;br /&gt;
Jawabannya kebijakan default Linux: &amp;#039;&amp;#039;&amp;#039;first-touch&amp;#039;&amp;#039;&amp;#039;. &amp;lt;code&amp;gt;malloc&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;new&amp;lt;/code&amp;gt; hanya memberi alamat &amp;#039;&amp;#039;virtual&amp;#039;&amp;#039; — belum ada halaman (page) fisik sama sekali. Halaman fisik baru dialokasikan saat pertama kali &amp;#039;&amp;#039;&amp;#039;ditulis&amp;#039;&amp;#039;&amp;#039;, dan ditempatkan di node memori tempat &amp;#039;&amp;#039;&amp;#039;thread yang menulisnya&amp;#039;&amp;#039;&amp;#039; sedang berjalan.&lt;br /&gt;
&lt;br /&gt;
Konsekuensinya brutal untuk pola kode yang paling umum:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
double *a = new double[N];&lt;br /&gt;
&lt;br /&gt;
for (long i = 0; i &amp;lt; N; ++i)      // init serial oleh thread master...&lt;br /&gt;
    a[i] = 0.0;                   // ...SEMUA halaman jatuh di node 0&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel for&lt;br /&gt;
for (long i = 0; i &amp;lt; N; ++i)      // 16 thread di 2 socket mengeroyok...&lt;br /&gt;
    a[i] = berat(a[i]);           // ...RAM node 0. Interconnect macet.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
INIT SERIAL:                            INIT PARALEL:&lt;br /&gt;
 Socket 0          Socket 1              Socket 0          Socket 1&lt;br /&gt;
 [RAM: a semua]    [RAM: kosong]         [RAM: a paruh 1]  [RAM: a paruh 2]&lt;br /&gt;
    ^  ^              |  |                  ^  ^              ^  ^&lt;br /&gt;
    T0 T1 ......... T8 T9                   T0 T1             T8 T9&lt;br /&gt;
         semua lewat sini                   lokal             lokal&lt;br /&gt;
         (interconnect = leher botol)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Perbaikannya satu baris: inisialisasi dengan loop paralel yang &amp;#039;&amp;#039;&amp;#039;pembagiannya sama&amp;#039;&amp;#039;&amp;#039; dengan loop komputasi:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#pragma omp parallel for schedule(static)&lt;br /&gt;
for (long i = 0; i &amp;lt; N; ++i)      // thread yang menyentuh duluan =&lt;br /&gt;
    a[i] = 0.0;                   // thread yang memakai nanti -&amp;gt; semua akses lokal&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuncinya &amp;lt;code&amp;gt;schedule(static)&amp;lt;/code&amp;gt; di &amp;#039;&amp;#039;&amp;#039;kedua&amp;#039;&amp;#039;&amp;#039; loop: pembagiannya deterministik, jadi thread t menyentuh dan memakai elemen yang sama. &amp;lt;code&amp;gt;dynamic&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;guided&amp;lt;/code&amp;gt; merusak jaminan ini (pembagian berubah tiap run) — di mesin NUMA, beban timpang pun sering lebih murah ditangani dengan &amp;lt;code&amp;gt;static,chunk&amp;lt;/code&amp;gt; daripada kehilangan lokalitas.&lt;br /&gt;
&lt;br /&gt;
= Benchmark: Membuktikannya =&lt;br /&gt;
&lt;br /&gt;
Triad ala [[Benchmark: STREAM|STREAM]] (a = b + s·c), bandingkan init serial vs paralel:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
#include &amp;lt;cstdio&amp;gt;&lt;br /&gt;
#include &amp;lt;cstring&amp;gt;&lt;br /&gt;
#include &amp;lt;omp.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Bandingkan: ./firsttouch serial  vs  ./firsttouch paralel&lt;br /&gt;
int main(int argc, char **argv) {&lt;br /&gt;
    const long N = 1L &amp;lt;&amp;lt; 26;               // 3 array x 512 MiB = 1.5 GiB&lt;br /&gt;
    const bool paralel = (argc &amp;gt; 1 &amp;amp;&amp;amp; strcmp(argv[1], &amp;quot;paralel&amp;quot;) == 0);&lt;br /&gt;
&lt;br /&gt;
    double *a = new double[N];             // alokasi virtual: belum ada halaman fisik&lt;br /&gt;
    double *b = new double[N];&lt;br /&gt;
    double *c = new double[N];&lt;br /&gt;
&lt;br /&gt;
    if (paralel) {&lt;br /&gt;
        // first-touch oleh thread yang sama yang nanti memakainya&lt;br /&gt;
        #pragma omp parallel for schedule(static)&lt;br /&gt;
        for (long i = 0; i &amp;lt; N; ++i) { a[i] = 0.0; b[i] = 1.0; c[i] = 2.0; }&lt;br /&gt;
    } else {&lt;br /&gt;
        // first-touch semuanya oleh thread master -&amp;gt; semua halaman di node 0&lt;br /&gt;
        for (long i = 0; i &amp;lt; N; ++i) { a[i] = 0.0; b[i] = 1.0; c[i] = 2.0; }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    double best = 1e99;&lt;br /&gt;
    for (int ulang = 0; ulang &amp;lt; 5; ++ulang) {&lt;br /&gt;
        double t0 = omp_get_wtime();&lt;br /&gt;
        #pragma omp parallel for schedule(static)&lt;br /&gt;
        for (long i = 0; i &amp;lt; N; ++i)&lt;br /&gt;
            a[i] = b[i] + 3.0 * c[i];&lt;br /&gt;
        double t1 = omp_get_wtime();&lt;br /&gt;
        if (t1 - t0 &amp;lt; best) best = t1 - t0;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // triad: baca b, baca c, tulis a = 24 byte per iterasi&lt;br /&gt;
    printf(&amp;quot;init %-7s : %.4f s per triad, %5.1f GB/s\n&amp;quot;,&lt;br /&gt;
           paralel ? &amp;quot;paralel&amp;quot; : &amp;quot;serial&amp;quot;, best, 24.0 * N / best / 1e9);&lt;br /&gt;
&lt;br /&gt;
    delete[] a; delete[] b; delete[] c;&lt;br /&gt;
    return 0;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Hasil nyata di laptop (i5-13450HX, &amp;#039;&amp;#039;&amp;#039;1 socket, 1 NUMA node&amp;#039;&amp;#039;&amp;#039;, 16 thread):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
init serial  : 0.0634 s per triad,  25.4 GB/s&lt;br /&gt;
init paralel : 0.0644 s per triad,  25.0 GB/s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Tidak ada bedanya — dan itu memang seharusnya.&amp;#039;&amp;#039;&amp;#039; Satu node NUMA berarti semua memori sama jauhnya; first-touch tidak mengubah apa-apa. Ini pelajaran penting: first-touch adalah masalah &amp;#039;&amp;#039;mesin multi-socket&amp;#039;&amp;#039;. Di node cluster 2-socket, benchmark yang sama tipikalnya menunjukkan init serial kehilangan hampir &amp;#039;&amp;#039;&amp;#039;setengah bandwidth&amp;#039;&amp;#039;&amp;#039; (semua thread mengantre ke satu memory controller + interconnect), sedangkan init paralel mendekati jumlah bandwidth kedua socket. Jalankan program di atas saat pertama kali dapat akses cluster — angkanya jauh lebih meyakinkan daripada artikel ini.&lt;br /&gt;
&lt;br /&gt;
== Jebakan C++: std::vector ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
std::vector&amp;lt;double&amp;gt; v(N);   // konstruktor MENG-NOL-KAN semua elemen...&lt;br /&gt;
                            // ...secara serial, di thread master.&lt;br /&gt;
                            // First-touch sudah terlanjur terjadi!&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;std::vector&amp;lt;double&amp;gt; v(N)&amp;lt;/code&amp;gt; melakukan value-initialization — itu sudah &amp;quot;menyentuh&amp;quot; seluruh array dari satu thread. Loop init paralel setelahnya percuma: halaman sudah punya rumah. Untuk array besar di mesin NUMA, pakai &amp;lt;code&amp;gt;new double[N]&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;std::unique_ptr&amp;lt;double[]&amp;gt;&amp;lt;/code&amp;gt; (tidak menginisialisasi, tidak menyentuh) lalu sentuh pertama kali lewat loop paralel — atau tulis custom allocator.&lt;br /&gt;
&lt;br /&gt;
= Affinity: Mengikat Thread ke Core =&lt;br /&gt;
&lt;br /&gt;
First-touch mengatur di mana &amp;#039;&amp;#039;data&amp;#039;&amp;#039; tinggal. Percuma kalau &amp;#039;&amp;#039;thread&amp;#039;&amp;#039;-nya sendiri berpindah-pindah: default-nya OS scheduler bebas memigrasikan thread antar core, dan begitu thread pindah socket, semua data yang tadinya lokal jadi remote — plus isi cache-nya ditinggal. Affinity = mengikat thread ke tempat tertentu. Dua environment variable utama (OpenMP ≥ 4.0):&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;OMP_PLACES&amp;lt;/code&amp;gt; — mendefinisikan &amp;quot;tempat&amp;quot; yang tersedia&lt;br /&gt;
: &amp;lt;code&amp;gt;threads&amp;lt;/code&amp;gt; = satu hardware thread per place; &amp;lt;code&amp;gt;cores&amp;lt;/code&amp;gt; = satu core fisik per place (hyperthread sesaudara jadi satu); &amp;lt;code&amp;gt;sockets&amp;lt;/code&amp;gt; = satu socket per place. Bisa juga eksplisit: &amp;lt;code&amp;gt;{0:8},{8:8}&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
; &amp;lt;code&amp;gt;OMP_PROC_BIND&amp;lt;/code&amp;gt; — mengatur cara thread dipetakan ke places&lt;br /&gt;
: &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; = tidak diikat (default GCC — thread bebas migrasi!); &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; = diikat, pemetaan terserah implementasi; &amp;lt;code&amp;gt;close&amp;lt;/code&amp;gt; = rapat: thread berurutan di place bertetangga; &amp;lt;code&amp;gt;spread&amp;lt;/code&amp;gt; = disebar merata ke seluruh places; &amp;lt;code&amp;gt;master&amp;lt;/code&amp;gt; = semua di place yang sama dengan master. Bisa juga per-region lewat klausa: &amp;lt;code&amp;gt;#pragma omp parallel proc_bind(spread)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Untuk melihat hasil pemetaannya, tidak perlu menebak — minta runtime melapor dengan &amp;lt;code&amp;gt;OMP_DISPLAY_AFFINITY=true&amp;lt;/code&amp;gt;. Demo nyata di laptop 16 CPU (10 core: 6 P-core ber-hyperthread + 4 E-core), 4 thread:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ export OMP_DISPLAY_AFFINITY=true OMP_AFFINITY_FORMAT=&amp;quot;thread %n -&amp;gt; core %A&amp;quot;&lt;br /&gt;
$ OMP_NUM_THREADS=4 OMP_PLACES=cores OMP_PROC_BIND=close ./program&lt;br /&gt;
thread 0 -&amp;gt; core 0-1&lt;br /&gt;
thread 1 -&amp;gt; core 2-3      &amp;lt;- rapat: 4 place pertama, semua P-core bertetangga&lt;br /&gt;
thread 2 -&amp;gt; core 4-5&lt;br /&gt;
thread 3 -&amp;gt; core 6-7&lt;br /&gt;
&lt;br /&gt;
$ OMP_NUM_THREADS=4 OMP_PLACES=cores OMP_PROC_BIND=spread ./program&lt;br /&gt;
thread 0 -&amp;gt; core 0-1&lt;br /&gt;
thread 1 -&amp;gt; core 6-7      &amp;lt;- disebar ke seluruh chip,&lt;br /&gt;
thread 2 -&amp;gt; core 12          sampai kena E-core (12, 14 tanpa hyperthread)&lt;br /&gt;
thread 3 -&amp;gt; core 14&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
(Notasi &amp;lt;code&amp;gt;0-1&amp;lt;/code&amp;gt; berarti place-nya satu core fisik dengan 2 hyperthread.)&lt;br /&gt;
&lt;br /&gt;
== close atau spread? ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Situasi !! Pakai !! Alasan&lt;br /&gt;
|-&lt;br /&gt;
| Memory-bound, thread &amp;lt; core, mesin multi-socket || &amp;lt;code&amp;gt;spread&amp;lt;/code&amp;gt; || Menyebar thread = memakai memory controller &amp;amp; cache L3 semua socket&lt;br /&gt;
|-&lt;br /&gt;
| Thread banyak berbagi data / sinkronisasi intensif || &amp;lt;code&amp;gt;close&amp;lt;/code&amp;gt; || Tetangga cache = komunikasi murah&lt;br /&gt;
|-&lt;br /&gt;
| Hybrid MPI+OpenMP, satu rank per socket || &amp;lt;code&amp;gt;close&amp;lt;/code&amp;gt; per rank || Rank sudah dipisah per socket; thread-nya jangan nyebrang&lt;br /&gt;
|-&lt;br /&gt;
| Thread = jumlah core || dua-duanya sama || Semua place terisi; yang penting &amp;#039;&amp;#039;&amp;#039;diikat&amp;#039;&amp;#039;&amp;#039; (bukan &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;)&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Resep standar yang aman untuk hampir semua kasus:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export OMP_PLACES=cores&lt;br /&gt;
export OMP_PROC_BIND=spread      # atau close; yang penting BUKAN false&lt;br /&gt;
export OMP_NUM_THREADS=&amp;lt;jumlah core fisik&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;OMP_PLACES=cores&amp;lt;/code&amp;gt; (bukan &amp;lt;code&amp;gt;threads&amp;lt;/code&amp;gt;) berarti satu thread OpenMP per core fisik — untuk kode HPC yang memory/FPU-bound, hyperthreading jarang membantu dan sering mengganggu.&lt;br /&gt;
&lt;br /&gt;
= Menyelaraskan Semuanya =&lt;br /&gt;
&lt;br /&gt;
Tiga komponen harus konsisten satu sama lain — lepas satu, dua lainnya ikut rusak:&lt;br /&gt;
&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Affinity&amp;#039;&amp;#039;&amp;#039;: thread diikat ke core (&amp;lt;code&amp;gt;OMP_PROC_BIND&amp;lt;/code&amp;gt; ≠ false) → pemetaan thread→socket stabil.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;First-touch&amp;#039;&amp;#039;&amp;#039;: array disentuh pertama kali oleh loop paralel → pemetaan data→socket mengikuti thread.&lt;br /&gt;
# &amp;#039;&amp;#039;&amp;#039;Schedule&amp;#039;&amp;#039;&amp;#039;: &amp;lt;code&amp;gt;schedule(static)&amp;lt;/code&amp;gt; di loop init &amp;#039;&amp;#039;dan semua&amp;#039;&amp;#039; loop komputasi → pemetaan iterasi→thread sama terus, dari init sampai timestep terakhir.&lt;br /&gt;
&lt;br /&gt;
Pola lengkapnya dalam kode simulasi tipikal:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;cpp&amp;quot;&amp;gt;&lt;br /&gt;
double *u = new double[N];         // jangan std::vector untuk grid besar&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel for schedule(static)&lt;br /&gt;
for (long i = 0; i &amp;lt; N; ++i)       // &amp;quot;init paralel&amp;quot; — pemanasan first-touch&lt;br /&gt;
    u[i] = kondisi_awal(i);&lt;br /&gt;
&lt;br /&gt;
for (int step = 0; step &amp;lt; STEPS; ++step) {&lt;br /&gt;
    #pragma omp parallel for schedule(static)   // pembagian SAMA dengan init&lt;br /&gt;
    for (long i = 0; i &amp;lt; N; ++i)&lt;br /&gt;
        u_baru[i] = update(u, i);&lt;br /&gt;
    std::swap(u, u_baru);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Alat Diagnosa =&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;numactl --hardware&amp;lt;/code&amp;gt; — berapa node, core mana milik siapa, RAM bebas per node.&lt;br /&gt;
* &amp;lt;code&amp;gt;numastat -p &amp;lt;PID&amp;gt;&amp;lt;/code&amp;gt; — halaman milik proses ada di node mana; cara paling langsung memergoki first-touch yang salah.&lt;br /&gt;
* &amp;lt;code&amp;gt;numactl --interleave=all ./program&amp;lt;/code&amp;gt; — paksa halaman disebar round-robin; obat instan (bukan optimal) untuk program orang lain yang init-nya serial.&lt;br /&gt;
* &amp;lt;code&amp;gt;OMP_DISPLAY_ENV=true&amp;lt;/code&amp;gt; — cetak semua setting OpenMP efektif saat program mulai.&lt;br /&gt;
* &amp;lt;code&amp;gt;lstopo&amp;lt;/code&amp;gt; (hwloc) — gambar topologi mesin: socket, cache, core.&lt;br /&gt;
* &amp;lt;code&amp;gt;likwid-pin&amp;lt;/code&amp;gt; / &amp;lt;code&amp;gt;likwid-perfctr&amp;lt;/code&amp;gt; — binding + pengukuran per-socket, lihat [[Profiling: likwid]].&lt;br /&gt;
&lt;br /&gt;
Satu catatan sistem: Linux punya &amp;#039;&amp;#039;&amp;#039;automatic NUMA balancing&amp;#039;&amp;#039;&amp;#039; (&amp;lt;code&amp;gt;/proc/sys/kernel/numa_balancing&amp;lt;/code&amp;gt;) yang diam-diam memigrasikan halaman mendekati thread yang mengaksesnya. Ini bisa menutupi dosa first-touch setelah beberapa detik — tapi dengan overhead page-fault, dan tidak bisa diandalkan. Saat benchmark serius, sadari fitur ini ada.&lt;br /&gt;
&lt;br /&gt;
= Jebakan Umum =&lt;br /&gt;
&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Lupa bahwa default-nya tidak diikat.&amp;#039;&amp;#039;&amp;#039; Tanpa &amp;lt;code&amp;gt;OMP_PROC_BIND&amp;lt;/code&amp;gt;, GCC membiarkan thread migrasi. Semua kerja keras first-touch bisa hangus saat OS memindahkan thread ke socket sebelah.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;&amp;lt;code&amp;gt;std::vector(N)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;calloc&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;memset&amp;lt;/code&amp;gt; setelah alokasi&amp;#039;&amp;#039;&amp;#039; — semuanya menyentuh array secara serial sebelum sempat di-first-touch paralel.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Memory pool / allocator yang mendaur ulang.&amp;#039;&amp;#039;&amp;#039; Halaman yang sudah pernah disentuh &amp;#039;&amp;#039;&amp;#039;tetap di node lamanya&amp;#039;&amp;#039;&amp;#039; — &amp;lt;code&amp;gt;free()&amp;lt;/code&amp;gt; lalu &amp;lt;code&amp;gt;malloc()&amp;lt;/code&amp;gt; lagi belum tentu memberi halaman perawan. Program panjang yang alokasinya didaur ulang tidak bisa mengandalkan first-touch begitu saja.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Granularitas halaman: 4 KiB = 512 double.&amp;#039;&amp;#039;&amp;#039; First-touch bekerja per halaman, bukan per elemen. Array kecil atau pembagian yang tidak sejajar halaman → beberapa halaman di perbatasan jadi &amp;quot;milik bersama&amp;quot;. Untuk array raksasa ini noise; jangan pusing.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Menguji di laptop lalu heran tidak ada efeknya&amp;#039;&amp;#039;&amp;#039; — seperti hasil benchmark di atas: 1 node NUMA = tidak ada remote access. Fenomena ini baru hidup di mesin multi-socket.&lt;br /&gt;
* &amp;#039;&amp;#039;&amp;#039;Hybrid MPI+OpenMP&amp;#039;&amp;#039;&amp;#039;: &amp;lt;code&amp;gt;mpirun&amp;lt;/code&amp;gt; sering membatasi cpuset tiap rank. Aturan mainnya: satu rank per socket/NUMA-node, OpenMP mengisi core di dalamnya dengan &amp;lt;code&amp;gt;close&amp;lt;/code&amp;gt;. Detail di [[OpenMP: Hybrid MPI+OpenMP]].&lt;br /&gt;
&lt;br /&gt;
= Ringkasan =&lt;br /&gt;
&lt;br /&gt;
* Halaman memori tinggal di node tempat thread yang &amp;#039;&amp;#039;&amp;#039;pertama menulisnya&amp;#039;&amp;#039;&amp;#039; — bukan yang &amp;lt;code&amp;gt;malloc&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Init serial = semua data numpuk di node 0 = semua thread socket lain antre di interconnect. Obatnya: &amp;#039;&amp;#039;&amp;#039;init paralel&amp;#039;&amp;#039;&amp;#039; dengan &amp;lt;code&amp;gt;schedule(static)&amp;lt;/code&amp;gt; yang sama dengan loop komputasi.&lt;br /&gt;
* Ikat thread-nya: &amp;lt;code&amp;gt;OMP_PLACES=cores&amp;lt;/code&amp;gt; + &amp;lt;code&amp;gt;OMP_PROC_BIND=spread/close&amp;lt;/code&amp;gt;. Tanpa binding, first-touch cuma janji manis.&lt;br /&gt;
* Verifikasi, jangan percaya: &amp;lt;code&amp;gt;OMP_DISPLAY_AFFINITY=true&amp;lt;/code&amp;gt; untuk thread, &amp;lt;code&amp;gt;numastat&amp;lt;/code&amp;gt; untuk data.&lt;br /&gt;
* Efeknya baru terasa di mesin multi-socket — laptop single-node tidak akan menunjukkannya (dibuktikan di atas: 25.4 vs 25.0 GB/s).&lt;br /&gt;
&lt;br /&gt;
= Lihat Juga =&lt;br /&gt;
&lt;br /&gt;
* [[NUMA]] — konsep dasarnya&lt;br /&gt;
* [[OpenMP: Parallel For &amp;amp; Scheduling]] — kenapa &amp;lt;code&amp;gt;schedule(static)&amp;lt;/code&amp;gt; deterministik itu penting di sini&lt;br /&gt;
* [[OpenMP: False Sharing]] — masalah lokalitas di skala cache line, saudara kecil masalah ini&lt;br /&gt;
* [[Benchmark: STREAM]] — benchmark bandwidth yang jadi template percobaan di atas&lt;br /&gt;
* [[Profiling: likwid]] — likwid-pin &amp;amp; pengukuran bandwidth per socket&lt;br /&gt;
* [[OpenMP: Hybrid MPI+OpenMP]] — pembagian socket antara MPI rank dan thread&lt;br /&gt;
* [[Cache line]]&lt;br /&gt;
&lt;br /&gt;
[[Kategori:HPC]]&lt;br /&gt;
[[Kategori:OpenMP]]&lt;/div&gt;</summary>
		<author><name>SanzSkuy</name></author>
	</entry>
</feed>