偽共享(False Sharing)是多執行緒程式設計中的一種效能問題,它發生在多個執行緒同時訪問不同的變數,但這些變數卻共享同一快取行(cache line)時。儘管這些變數並不相互依賴,但由於它們的儲存位置在快取中靠得很近,導致處理器頻繁地無效化(invalidate)快取行,從而影響效能。
快取行的基本概念
在現代計算機中,CPU 使用快取來提高記憶體訪問速度。快取通常分為多個層次(L1、L2、L3),每個層次的容量和訪問速度各不相同。CPU 從主記憶體讀取資料時,通常是以快取行(通常是 64 位元組)為單位進行讀取的。這意味著,訪問快取行中的一個位元組將導致整個快取行被載入到 CPU 的快取中。
偽共享的發生
偽共享通常發生在以下情況下:
多個執行緒:多個執行緒併發地訪問和修改不同的變數。
相同的快取行:這些變數位於同一個快取行內,導致 CPU 每次更新某個變數時都要使得包含其他變數的快取行失效。
例如,考慮以下程式碼:
#include <iostream> #include <thread> #include <vector> #include <chrono> const int NUM_THREADS = 4; const int NUM_ITERATIONS = 10000000; struct Data { int a; // 變數 a int b; // 變數 b // 可能存在偽共享 }; Data data[NUM_THREADS]; void increment(int index) { for (int i = 0; i < NUM_ITERATIONS; ++i) { data[index].a++; // 執行緒修改 a data[index].b++; // 執行緒修改 b } } int main() { std::vector<std::thread> threads; for (int i = 0; i < NUM_THREADS; ++i) { threads.emplace_back(increment, i); } for (auto& t : threads) { t.join(); } return 0; }
在這個示例中,data
陣列的每個元素都有兩個整數變數 a
和 b
。假設這兩個變數儲存在同一個快取行中,當多個執行緒同時修改 a
和 b
時,可能會導致偽共享。這是因為每個執行緒在更新 a
時,可能會導致儲存 b
的快取行失效,從而引發頻繁的快取行無效化。
偽共享的影響
偽共享會導致以下幾個問題:
效能下降:由於頻繁的快取行無效化,CPU 可能會花費大量時間在資料一致性上,從而影響整體效能。
增加的延遲:每次訪問共享快取行時,CPU 需要等待,從而增加了延遲。
吞吐量降低:在多執行緒環境中,偽共享可能導致 CPU 的利用率下降,影響吞吐量。
如何避免偽共享
避免偽共享的方法主要包括:
記憶體對齊:透過確保變數在記憶體中對齊到快取行邊界,可以減少偽共享的發生。例如,使用
alignas
關鍵字來對齊結構體。struct alignas(64) Data { int a; int b; };
增加快取行的使用:將變數放置在不同的快取行中,可以有效避免偽共享。例如,在結構體中新增填充變數(padding)以確保不同變數位於不同的快取行。
struct Data { int a; char padding[60]; // 填充到 64 位元組 int b; };
分離數據結構:在多執行緒應用中,將資料分散到不同的結構體中,每個執行緒使用獨立的結構體,避免共享資料。
使用原子操作:在某些情況下,可以使用原子操作(如
std::atomic
)來減少對共享變數的直接訪問,降低偽共享的風險。
總結
偽共享是多執行緒程式設計中的一個常見效能問題,它發生在多個執行緒同時訪問位於同一快取行中的不同變數時。透過合理的記憶體對齊和數據結構設計,可以有效避免偽共享,提升多執行緒應用的效能。在進行高效能多執行緒開發時,瞭解偽共享及其影響至關重要。