文章目錄
- 1 什么是偽共享?
- 2 為什么對齊?
- 3 偽共享的實際影響
- 4 為什么必須是 64 字節?
- 5 其他替代方案
- 6 驗證對齊效果
- 總結
1 什么是偽共享?
偽共享是 多線程編程中的一種性能問題,其本質是:
- 緩存行(Cache Line):現代 CPU 的緩存以固定大小的塊(緩存行)為單位管理數據,常見緩存行大小為 64 字節(如 x86/x64 CPU)。
- 共享緩存行:如果兩個獨立的變量(如
_bottom
和_top
)位于同一個緩存行中,即使它們被不同線程訪問(如一個線程寫_bottom
,另一個線程讀_top
),也會導致緩存行被頻繁標記為“無效”(Cache Invalidation)。 - 性能損失:頻繁的緩存同步(從 L1/L2 緩存到主存)會顯著降低程序性能。
避免偽共享(False Sharing),從而提升多線程程序的性能
2 為什么對齊?
以64字節對齊為例:
通過 alignas(64)
強制變量對齊到 64 字節邊界,可以確保:
- 獨占緩存行:每個變量(如
_bottom
和_top
)獨占一個完整的緩存行(64 字節),避免與其他變量共享。 - 隔離修改:當一個線程修改
_bottom
時,不會觸發其他線程中_top
所在緩存行的無效化,反之亦然。
示例內存布局:
// 未對齊時(偽共享)
Cache Line 0: | _bottom (8 bytes) | _top (8 bytes) | ...(剩余 48 字節)|// 對齊到 64 字節后(避免偽共享)
Cache Line 0: | _bottom (8 bytes) | padding (56 bytes) |
Cache Line 1: | _top (8 bytes) | padding (56 bytes) |
在 32 位 ARM 系統中,為了避免偽共享(False Sharing),通常建議的對齊大小為 32 字節或 64 字節,具體取決于目標處理器的緩存行(Cache Line)大小。
- ARM 系統的緩存行大小
32 位 ARM 處理器的緩存行大小因架構和型號而異:
- 較舊 ARMv6/ARMv7 處理器(如 Cortex-A8/A9)通常使用 32 字節 的緩存行。
- 較新 ARMv7/ARMv8 處理器(如 Cortex-A53/A72)可能采用 64 字節 的緩存行(與 x86/x64 對齊)。
具體需查閱目標 CPU 的文檔(如 Technical Reference Manual)確認。
- 對齊建議
- 通用保守方案:32 字節對齊
若不確定目標處理器的緩存行大小,可保守對齊到 32 字節(覆蓋舊型號的 32 字節緩存行):
alignas(32) std::atomic<size_t> _bottom;
alignas(32) std::atomic<size_t> _top;
- 針對新型號:64 字節對齊
若目標處理器為較新的 ARMv8 或已知緩存行為 64 字節(如部分 Cortex-A 系列),可直接對齊到 64 字節:
alignas(64) std::atomic<size_t> _bottom;
alignas(64) std::atomic<size_t> _top;
- C++17 自適應方案
若支持 C++17,使用std::hardware_destructive_interference_size
自動適配緩存行大小:
#include <new>
alignas(std::hardware_destructive_interference_size) std::atomic<size_t> _bottom;
alignas(std::hardware_destructive_interference_size) std::atomic<size_t> _top;
- 驗證對齊效果
通過代碼檢查變量地址是否為對齊值的整數倍:
#include <iostream>
#include <cstdint>int main() {alignas(32) std::atomic<size_t> _bottom;alignas(32) std::atomic<size_t> _top;// 檢查對齊是否成功auto check_alignment = [](const auto& var, size_t alignment) {uintptr_t addr = reinterpret_cast<uintptr_t>(&var);return (addr % alignment == 0) ? "Success" : "Failed";};std::cout << "_bottom 對齊 32: " << check_alignment(_bottom, 32) << "\n";std::cout << "_top 對齊 32: " << check_alignment(_top, 32) << "\n";return 0;
}
- 實際性能對比
場景 | 32 字節緩存行(對齊 32) | 64 字節緩存行(對齊 64) | 未對齊(偽共享) |
---|---|---|---|
線程競爭開銷 | 低 | 低(若緩存行為 64) | 高(頻繁緩存失效) |
內存占用 | 略高(填充空間) | 更高(填充空間) | 低 |
- 適用場景總結
- 嵌入式/IoT 設備(舊款 ARMv6/v7):優先對齊 32 字節。
- 高性能 ARM 處理器(如 Cortex-A72):對齊 64 字節。
- 跨平臺代碼:使用 C++17 的
std::hardware_destructive_interference_size
。
在 32 位 ARM 系統中,若無明確緩存行信息,默認對齊到 32 字節是安全選擇。
若針對新型處理器或已知緩存行為 64 字節,則對齊到 64 字節。通過合理對齊,可顯著減少偽共享帶來的性能損失。
3 偽共享的實際影響
假設 _bottom
和 _top
是一個無鎖隊列的頭尾指針:
- 線程 A 頻繁修改
_bottom
(入隊操作)。 - 線程 B 頻繁修改
_top
(出隊操作)。 - 如果它們共享同一個緩存行,每次修改都會導致對方線程的緩存失效,性能可能下降 數倍甚至數十倍。
通過對齊到 64 字節,兩者的修改完全隔離,避免不必要的緩存同步。
4 為什么必須是 64 字節?
- 緩存行大小:x86/x64 CPU 的緩存行大小通常為 64 字節,ARM 架構也普遍采用 64 字節。對齊到緩存行大小是最直接的方法。
- 兼容性:即使某些 CPU 的緩存行更小(如 32 字節),對齊到 64 字節也能覆蓋所有常見情況。
5 其他替代方案
- 填充字節(Padding)
手動在變量間插入填充數據,強制隔離:
struct AlignedData {std::atomic<size_t> _bottom;char padding[64 - sizeof(std::atomic<size_t>)];std::atomic<size_t> _top;
};
- 缺點:需手動計算填充大小,不夠靈活。
- C++17
std::hardware_destructive_interference_size
C++17 提供了表示緩存行大小的常量:
#include <new>
alignas(std::hardware_destructive_interference_size) std::atomic<size_t> _bottom;
alignas(std::hardware_destructive_interference_size) std::atomic<size_t> _top;
- 優點:代碼可移植,自動適配不同平臺的緩存行大小。
- 缺點:需要 C++17 支持。
6 驗證對齊效果
可以通過以下方式驗證變量是否對齊到 64 字節:
#include <iostream>
#include <cstdint>int main() {alignas(64) std::atomic<size_t> _bottom;alignas(64) std::atomic<size_t> _top;// 檢查地址是否為 64 的倍數std::cout << "Address of _bottom: " << &_bottom << " (aligned: "<< ((reinterpret_cast<uintptr_t>(&_bottom) % 64 == 0) << ")\n";std::cout << "Address of _top: " << &_top << " (aligned: "<< ((reinterpret_cast<uintptr_t>(&_top) % 64 == 0) << ")\n";return 0;
}
總結
- 對齊到 64 字節:通過獨占緩存行,避免
_bottom
和_top
之間的偽共享。 - 適用場景:高頻并發訪問的原子變量(如無鎖數據結構、計數器等)。
- 推薦方法:優先使用
alignas(64)
或 C++17 的std::hardware_destructive_interference_size
。