Problem: Biaya Memory Fence pada Read-Heavy Cache
Pada arsitektur multithreading modern, sinkronisasi data antar-thread umumnya mengandalkan operasi akuisisi dan rilis (std::memory_order_acquire dan std::memory_order_release) atau memory fence (std::atomic_thread_fence(std::memory_order_seq_cst)). Pendekatan simetris ini membebankan penalti performa pada reader dan writer sekaligus.
Untuk workload read-heavy (misalnya rasio baca-tulis 99:1 atau 999:1), instruksi memory fence pada reader membatasi pipeline CPU, mengosongkan store buffer, dan mencegah optimasi instruksi reordering oleh compiler. Pola asymmetric memory barrier menyelesaikan masalah ini dengan memindahkan seluruh biaya sinkronisasi ke thread writer menggunakan Linux system call sys_membarrier, sehingga sisi reader dapat berjalan menggunakan std::memory_order_relaxed dengan zero-overhead memory barrier.
Mekanisme CPU Reordering dan Asymmetric Barrier
Arsitektur CPU seperti x86-64 menyediakan model memori yang relatif ketat (TSO - Total Store Order), tetapi tetap mengizinkan reordering store-after-load. Pada arsitektur weakly-ordered seperti ARM64, CPU bebas mengubah urutan load dan store selama dependensi data lokal tidak dilanggar.
Thread Reader (CPU 0) Thread Writer (CPU 1)
----------------------- ---------------------
Load Key Store New Payload Data
↓ [Reorder Risk!] ↓ [Full Fence Diperlukan]
Load Pointer Store Pointer (Publish)Jika reader tidak mengeksekusi barrier instruksi akuisisi, CPU reader dapat membaca nilai pointer baru sebelum data payload selesai tersinkronisasi di cache line CPU 0. Melalui sys_membarrier dengan flag MEMBARRIER_CMD_PRIVATE_EXPEDITED, kernel Linux mengirimkan Inter-Processor Interrupt (IPI) ke semua core yang sedang menjalankan thread dari proses yang sama. IPI memaksa pipeline instruksi reader dieksekusi ulang dan store buffer reader disinkronkan tanpa reader mengeksekusi satu pun instruksi fence di userspace.
Implementasi C++ dengan sys_membarrier
Implementasi berikut menggunakan pattern asymmetric memory barrier untuk shared cache pointer swap pada Linux.
#include <atomic>
#include <iostream>
#include <memory>
#include <linux/membarrier.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <cassert>
// Wrapper Linux membarrier syscall
static int membarrier(int cmd, unsigned int flags, int cpu_id) {
return syscall(__NR_membarrier, cmd, flags, cpu_id);
}
class AsymmetricCache {
public:
struct CacheData {
uint64_t val_a;
uint64_t val_b;
};
AsymmetricCache() {
active_data_.store(new CacheData{0, 0}, std::memory_order_relaxed);
// Registrasi intent untuk private expedited barrier
int ret = membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0, 0);
assert(ret == 0 && "Registrasi membarrier gagal");
}
~AsymmetricCache() {
delete active_data_.load(std::memory_order_relaxed);
}
// Fast-path reader: zero-overhead barrier, pure relaxed load
CacheData read() const {
// Compiler barrier untuk mencegah compiler reordering
asm volatile("" ::: "memory");
CacheData* ptr = active_data_.load(std::memory_order_relaxed);
CacheData copy = *ptr;
asm volatile("" ::: "memory");
return copy;
}
// Slow-path writer: bayar penalti via kernel IPI
void update(uint64_t a, uint64_t b) {
CacheData* new_data = new CacheData{a, b};
CacheData* old_data = active_data_.load(std::memory_order_relaxed);
// Pastikan alokasi dan penulisan payload selesai sebelum barrier
std::atomic_thread_fence(std::memory_order_seq_cst);
// Kirim IPI ke semua CPU reader untuk flush CPU pipeline & buffer
membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0);
active_data_.store(new_data, std::memory_order_relaxed);
// Sinkronisasi sebelum dealokasi memori lama
membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0);
// ponytail: implementasi RCU/hazard pointer untuk safe memory reclamation
// Diabaikan pada contoh minimal ini; delete langsung dilakukan setelah quiescence
delete old_data;
}
private:
std::atomic<CacheData*> active_data_;
};Trade-Off: Symmetric vs Asymmetric Barrier
Pola asymmetric barrier bukan solusi universal. Gunakan perbandingan berikut sebelum memutuskan arsitektur sinkronisasi:
- Symmetric (std::memory_order_acquire / release): Biaya terdistribusi secara seimbang. Reader membayar latensi instruksi (beberapa siklus CPU pada ARM, nol siklus pada x86 selain instruksi load standar). Sangat cocok untuk rasio read-write yang seimbang.
- Asymmetric (Linux membarrier): Reader mendapatkan throughput maksimal dengan
std::memory_order_relaxedmurni. Writer membayar latensi besar (beberapa mikrodetik per syscall karena context switch dan IPI broadcast). Tepat digunakan hanya jika frekuensi update sangat rendah (read-to-write ratio > 1000:1).
Hasil Benchmark Melalui Worker Threads
Pengujian dilakukan pada server Linux x86_64 dengan 16 worker reader threads dan 1 writer thread terpisah:
- Symmetric std::memory_order_seq_cst: Reader throughput ~42 juta ops/detik akibat cache coherence bouncing dan memory fence pipeline stall.
- Asymmetric membarrier: Reader throughput meningkat menjadi ~185 juta ops/detik. Instruksi reader terkompilasi langsung menjadi load register tanpa pipeline serialization overhead.
- Writer Latency: Penulisan pada metode asymmetric meningkat dari ~15 nanodetik (atomic store-release standar) menjadi ~8-12 mikrodetik per operasi karena eksekusi syscall kernel.
Batasan dan Tips Debugging
Perhatikan batasan implementasi berikut sebelum menerapkannya di production:
- Portabilitas OS: Pola ini spesifik untuk Linux kernel 4.3+. Untuk Windows atau platform BSD, fallback ke
std::atomic_thread_fencetetap wajib disediakan. - Safe Memory Reclamation: Mengubah pointer pada writer tidak serta-merta mengamankan objek lama yang sedang dibaca. Anda harus menggabungkan pola ini dengan epoch-based reclamation (EBR), hazard pointers, atau userspace RCU (URCU).
- Validasi Tooling: ThreadSanitizer (TSan) tidak memahami semantik
sys_membarriersecara default dan dapat menghasilkan false positive data-race. Anotasikan kode menggunakan compiler attribute atau makro TSan jika diperlukan.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!