False Sharing
2026-01-31 · 3 min read
CPU'larda bellek , cache line adı verilen sabit boyutlu bloklar (çoğunlukla 64 bayt) halinde önbelleğe (cache) alınır. False sharing, birbirinden bağımsız değişkenlerin aynı cache line içerisinde yer alması ve bu değişkenlerin farklı çekirdekler tarafından eşzamanlı olarak değiştirilmesi durumunda ortaya çıkar.
Linux Kernel dokümantasyonunda verilen aşağıdaki yapıyı göz önüne alalım:
struct foo {
refcount_t refcount;
...
char name[16];
} ____cacheline_internodealigned_in_smp;
refcount (A) ve name (B) üyeleri (member) aynı cache line'ı paylaşır:
+-----------+ +-----------+
| CPU 0 | | CPU 1 |
+-----------+ +-----------+
/ |
/ |
V V
+----------------------+ +----------------------+
| A B | Cache 0 | A B | Cache 1
+----------------------+ +----------------------+
| |
---------------------------+------------------+-----------------------------
| |
+----------------------+
| |
+----------------------+
Main Memory | A B |
+----------------------+
name nesne yaratılırken atanır ve sonrasında sadece
okunur ancak, refcount sık sık güncellenir. Aynı
cache line'da oldukları için
refcount güncellendiğinde cache invalidate edilir
ve okuma yapan diğer CPU'ların cache line'ı yeniden
yüklemesi gerekir.
Her cache line paylaşımı problem değildir, performans sorunu yaratması için genellikle şu koşullar gereklidir:
- Birden fazla çekirdek tarafından erişilen veri
- Bu veriye, en az biri yazma operasyonu (yazma/okuma veya yazma/yazma) olmak üzere eşzamanlı erişim
Linux üzerinde, perf c2c ve
pahole gibi araçlar kullanılarak false sharing
problemleri tespit edilebilir.
False sharingden kaçınmak için Linux Kernel dokümantasyonunda aşağıdaki yöntemler belirtilmiştir:
- Sık erişilen (hot) global veriyi ayrı bir cache line'a taşımak:
#include <stdalign.h>
#include <stdint.h>
#ifndef CACHELINE_SIZE
#define CACHELINE_SIZE 64
#endif
alignas(CACHELINE_SIZE) volatile uint64_t tcp_memory_allocated = 0;
Bu yöntem daha fazla bellek kullanımına neden olabilir.
- False sharing’e neden olan alanlar aynı veri yapısı içinde bulunuyorsa, bu alanlar farklı cache line’lara düşecek şekilde yeniden düzenlenebilir:
#include <stdint.h>
#include <stdalign.h>
#ifndef CACHELINE_SIZE
#define CACHELINE_SIZE 64
#endif
struct stats_bad {
// HOT: sık yazılan
volatile uint64_t hot_counter;
// COLD: çoğunlukla okunan / nadir yazılan
char name[32];
uint32_t flags;
};
struct stats_good {
// HOT: tek başına bir cache line'a yakın konumlandır
alignas(CACHELINE_SIZE) volatile uint64_t hot_counter;
// Diğer alanların aynı cache line'ı paylaşmasını önle
char _pad[CACHELINE_SIZE - sizeof(uint64_t)];
// COLD
char name[32];
uint32_t flags;
};
Yeni yerleşim başka alanlar arasında yeni false sharing problemleri yaratabilir, bu nedenle bu yöntem dikkatli kullanılmalıdır.
- False sharing’in temel tetikleyicisi write operasyonlarıdır. Bu nedenle mümkün olan her yerde koşulsuz write yerine, read-then-write (compare-then-write) yaklaşımı tercih edilmelidir:
set_bit(XXX); // bunun yerine
if (!test_bit(XXX))
set_bit(XXX);
- Sık güncellenen global sayaçlar yerine, per-cpu data + global aggregation yöntemleri kullanılmalıdır. Thread-local değişkenler belirli periyotlarla aggregate edilip global değişkene atanabilir.
Bir sonraki yazıda görüşmek üzere.