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链接库

posted @ 2022-09-15 10:04  秋来叶黄  阅读(34)  评论(0编辑  收藏  举报