用戶空間消除緩存行偽共享的方法
2個處理器寫到不同的物理地址,但是2個物理地址映射到同一個緩存行,這種情況稱為緩存行偽共享(False Sharing),造成的危害是:因為緩存一致性協(xié)議而生成大量通信,導致性能下降。消除緩存行偽共享的方法是把不相關(guān)的2個數(shù)據(jù)放在不同的緩存行。
可以使用GCC編譯器提供的屬性“aligned”修飾結(jié)構(gòu)體的字段、結(jié)構(gòu)體或者變量。可以選擇指定名稱前后帶有“__”(2個下劃線)的屬性名稱,在頭文件中使用它們,不必擔心可能有名稱相同的宏,例如使用屬性名稱“aligned”取代“aligned”。
為了使用方便,自己定義宏__cacheline_aligned如下。目前主流的處理器緩存的行大小是64字節(jié),所以把宏L1_CACHE_LINE_SIZE的默認值設(shè)置為64。
#if !defined(L1_CACHE_LINE_SIZE)
#define L1_CACHE_LINE_SIZE 64
#endif
#define __cacheline_aligned __attribute__((__aligned__(L1_CACHE_LINE_SIZE)))
#define __cacheline_aligned attribute((aligned(L1_CACHE_LINE_SIZE))) 可以使用宏__cacheline_aligned修飾結(jié)構(gòu)體的字段,確保這個字段在結(jié)構(gòu)體里面的偏移對齊到一級緩存行的長度,也就是偏移是一級緩存行的長度的整數(shù)倍。
可以使用宏__cacheline_aligned修飾結(jié)構(gòu)體,確保這個結(jié)構(gòu)體的長度對齊到一級緩存行的長度,并且類型是這個結(jié)構(gòu)體的變量(包括全局變量和局部變量)的地址對齊到一級緩存行的長度。
可以使用宏__cacheline_aligned修飾變量,確保變量的地址對齊到一級緩存行的長度。
1.動態(tài)分配結(jié)構(gòu)體
例如結(jié)構(gòu)體s定義如下,要把字段m1和m2分別放在2個緩存行中,使用宏__cacheline_aligned修飾字段m2可以確保這個字段在結(jié)構(gòu)體里面的偏移對齊到一級緩存行的長度,使用宏__cacheline_aligned修飾結(jié)構(gòu)體可以確保結(jié)構(gòu)體的長度對齊到一級緩存行的長度。要想使得字段m2的地址是一級緩存行的長度的整數(shù)倍,必須確保動態(tài)分配的結(jié)構(gòu)體的起始地址是一級緩存行的長度的整數(shù)倍,并且字段m2在結(jié)構(gòu)體里面的偏移是一級緩存行的長度的整數(shù)倍。
struct s {
int m1;
int m2 __cacheline_aligned;
} __cacheline_aligned;
可以使用函數(shù)posix_memalign()或aligned_alloc()給結(jié)構(gòu)體動態(tài)分配內(nèi)存,這兩個函數(shù)的原型如下。
#include <stdlib.h>
int posix_memalign(void **memptr, size_t alignment, size_t size);
void *aligned_alloc(size_t alignment, size_t size);
int posix_memalign(void **memptr, size_t alignment, size_t size); void aligned_alloc(size_t alignment, size_t size); 函數(shù) posix_memalign()分配size字節(jié)的內(nèi)存,把分配的內(nèi)存的地址存放在memptr中。分配的內(nèi)存的地址是參數(shù)alignment的整數(shù)倍,參數(shù)alignment必須是2的冪,并且是sizeof(void *)的整數(shù)倍。
函數(shù)aligned_alloc()是C11標準定義的函數(shù),參數(shù)size必須是參數(shù)alignment的整數(shù)倍。分配的內(nèi)存的地址是參數(shù)alignment的整數(shù)倍,參數(shù)alignment必須是2的冪,并且是sizeof(void *)的整數(shù)倍。函數(shù)aligned_alloc()返回分配的內(nèi)存的地址。
2.靜態(tài)分配結(jié)構(gòu)體
如果使用宏__cacheline_aligned修飾結(jié)構(gòu)體,那么這個結(jié)構(gòu)體的長度是一級緩存行的長度的整數(shù)倍,并且類型是這個結(jié)構(gòu)體的變量(包括全局變量和局部變量)的地址是一級緩存行的長度的整數(shù)倍。下面是一個例子。
#include <stdio.h>
#if !defined(L1_CACHE_LINE_SIZE)
#define L1_CACHE_LINE_SIZE 64
#endif
#define __cacheline_aligned __attribute__((__aligned__(L1_CACHE_LINE_SIZE)))
struct s {
int m1;
int m2 __cacheline_aligned;
} __cacheline_aligned;
struct s g;
int main(int argc, char *argv[])
{
struct s v;
printf("sizeof(g)=%lu, &g=%p, &g %% L1_CACHE_LINE_SIZE = %lu\n",
sizeof(g), &g, (((unsigned long)&g) % L1_CACHE_LINE_SIZE));
printf("sizeof(v)=%lu, &v=%p, &v %% L1_CACHE_LINE_SIZE = %lu\n",
sizeof(v), &v, (((unsigned long)&v) % L1_CACHE_LINE_SIZE));
return 0;
}
運行程序,從打印的信息可以看到:g和v的長度都是128字節(jié),地址是64的整數(shù)倍。
如果使用宏__cacheline_aligned修飾變量,那么變量的地址是一級緩存行的長度的整數(shù)倍,變量的長度不一定是一級緩存行的長度的整數(shù)倍。下面是一個例子。
#include <stdio.h>
#if !defined(L1_CACHE_LINE_SIZE)
#define L1_CACHE_LINE_SIZE 64
#endif
#define __cacheline_aligned __attribute__((__aligned__(L1_CACHE_LINE_SIZE)))
struct s {
int m1;
int m2;
};
struct s g __cacheline_aligned;
int main(int argc, char *argv[])
{
struct s v __cacheline_aligned;
printf("sizeof(g)=%lu, &g=%p, &g %% L1_CACHE_LINE_SIZE = %lu\n",
sizeof(g), &g, (((unsigned long)&g) % L1_CACHE_LINE_SIZE));
printf("sizeof(v)=%lu, &v=%p, &v %% L1_CACHE_LINE_SIZE = %lu\n",
sizeof(v), &v, (((unsigned long)&v) % L1_CACHE_LINE_SIZE));
return 0;
}
運行程序,從打印的信息可以看到:g和v的長度都是8字節(jié),地址是64的整數(shù)倍。
3.參考文檔
(1) GCC的公共函數(shù)屬性aligned,https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-aligned-function-attribute
(2) GCC的公共變量屬性aligned,https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html#index-aligned-variable-attribute
(3) GCC的公共類型屬性aligned,https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.html#index-aligned-type-attribute
(4) posix_memalign, https://man7.org/linux/man-pages/man3/posix_memalign.3.html