intel false sharing 伪共享
介绍
在cpu设计中,有各种缓存,目前常见的是cache1 2 3(缓存1 缓存2 缓存3)三层,cache1更靠近cpu,空间更小,速度更快。cache1->cache2->cache3->内存 依次距离cpu更远,空间更大,速度更慢。缓存一般设计是由缓存行实现,每行64字节。也就是每个缓存中包含很多缓存行,每个缓存行64字节(看做一个整体)。cpu从内存把数据加载进缓存时,就是按照连续的64字节加载的,因为cpu认为如果内存中的一个数据被使用,相邻的其他数据在最近的时间段内被使用的概率更大,所以全部加载进来。
如果数据没有修改,那么下次再访问就可以命中,直接从缓存获取数据,而不用到内存中去。如果数据被修改,那么相关联的一个缓存行64字节数据都会失效,下次读取数据要重新从内存获取。
这里就有一个问题,a和b变量相邻,如果a和b都频繁修改,或者其中一个频繁修改,那么必然会影响到另一个。因为缓存是按照缓存行(64字节)作为最小处理单位,如果a修改,a相关联的缓存行(包含b的数据)都会失效,下次读取b(虽然b没有修改)也无法命中,必须从内存获取,这就叫做伪共享。
这实际上是一个正常的现象,一般不用考虑,但是有些特殊情况,需要进行调优。如何避免这种问题?可以在a和b中间增加64字节的数据,把a和b强行分到两个cache line(如果a和b是在同一个结构体或者类似于数组相邻)。
这个技巧有什么用呢,就是如果你有一个结构体,在代码中需要频繁的使用,定义了这个结构体的数组等类似的情况,那么可以使用__attribute__((aligned(64)));
让结构体按照64字节对齐。这个是gcc的参数,意思是如果结构体最大变量不足64字节,就按照64字节对齐;如果大于64字节,则按照对应的长度对齐。
示例
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
struct MyStruct
{
long a;
char c[64];
long b;
}__attribute__((aligned(64)));//这里去掉会大大影响性能
void *threadf(void * arg)
{
struct MyStruct * a = (struct MyStruct*)arg;
for(int i = 0; i < 100000000; i++)
{
a->b++;
}
}
void *threadf1(void * arg)
{
struct MyStruct * a = (struct MyStruct*)arg;
for(int i = 0; i < 100000000; i++)
{
a->a++;
}
}
int main()
{
printf("size[%d]\n", sizeof(struct MyStruct));
clock_t begin, end;
begin = clock();
pthread_t t1, t2;
struct MyStruct ms[2];
int res = pthread_create(&t1, NULL, threadf, &ms[0]);
if(res != 0)
{
printf("err\n");
}
res = pthread_create(&t2, NULL, threadf1, &ms[1]);
if(res != 0)
{
printf("err\n");
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
end = clock();
double seconds =(double)(end - begin)/CLOCKS_PER_SEC;
printf("Use time is: %.8f\n", seconds);
return 0;
}
gcc编译增加-lpthread
链接库