缓存行/伪共享问题,验证优化

缓存行是数据缓存中的一个固定大小的区域,它的大小通常为64字节或者更大。每个缓存行可以保存多个内存地址上的数据。当CPU需要访问某个内存地址时,它首先检查缓存中是否已经加载了该地址的数据。如果数据存在于缓存行中,则称为缓存命中(cache hit),CPU可以直接从缓存中读取数据,无需访问主内存,这样可以提高访问速度。如果数据不在缓存中,则称为缓存未命中(cache miss),CPU需要从主内存中加载数据到缓存中,并且可能会替换掉缓存中的某条数据。

缓存行,伪共享

当CPU要读取一个数据时,首先从L1缓存查找,命中则返回;若未命中,再从L2缓存中查找,如果还没有则从L3缓存查找(如果有L3缓存的话)。如果还是没有,则从内存中查找,并将读取到的数据逐级放入缓存。

每次读数据的时候都会 一次获取一整块的内存数据,放入缓存,那么这么一块数据就被称作缓存行。缓存行是cpu缓存分配的最小的存储单元,目前64位架构下,64字节最为常用。。。。。

伪共享问题:

多核多线程并发场景下,多核要操作的不同变量处于同一缓存行,某cpu更新缓存行中数据,并将其写回缓存,同时其他处理器会使该缓存行失效,如需使用,还需从内存中重新加载。这对效率产生了较大的影响。

比如说我有个结构体占16字节,但是程序运行读的话会一次读64字节进去,此时a和b就处在同一缓存行上,那么比如双核下,去操作这个结构体的数据,,

struct AA
{
	long a;//8B
	long b;//8B
};

比如core1修改a数据之后,需要满足缓存一致性协议(core2发现自己缓存的数据跟core1不一样了,core2怎么知道呢? 有个机制:某个 CPU 核心更新了 Cache 中的数据,总线把这个事件广播通知给其他所有的核心),core2需将他的缓存行置为失效,core1要把新数据写回内存,core2去从内存读新数据

轮到core2去修改b数据,core2修改完之后,需要满足缓存一致性协议,core1发现自己的缓存跟内存不一样了,core1把缓存行置为失效,然后叫core2把新数据写回到内存,然后core1就要重新从内存读新数据到自己的缓存,

接着core1又去修改a数据,这下core2可发现自己缓存里面的a数据跟跟内存里的a不一样了,因此轮到core2改数据的时候,core2就得先从内存读数据到自己缓存,然后自己再去修改b.

这么下去会发现,每次己方的修改都会导致对方下一次的缓存无法命中,从而每次都要从内存重新读数据拷贝过去,这样有损性能

这个问题就叫伪共享

验证这个问题:

假设两个线程跑到不同的core上去操作数据

struct AB
{
	long a;//8B
  //long arr[7];

	long b;//8B
  //long brr[7];
};
AB ab{0,0};

//假设开两个线程,跑到不同的core上
//模拟core1 操作a
void funa()
{
  for(int i=0;i<100000000;++i)
  {
    ab.a++;
  }
}
//模拟core2操作b
void funb()
{
  for(int i=0;i<100000000;++i)
  {
    ab.b++;
  }
}
int main()
{

    clock_t start,end;
    start=clock();

    thread t1(funa);
    thread t2(funb);

    t1.join();
    t2.join();
    end=clock();
    cout<<"用时:"<(end-start)/CLOCKS_PER_SEC<

用时:0.767


解决优化办法就是缓存行填充(也称缓存行对齐)

我们把注释放开,如下:

struct AB
{
	long a;//8B
  long arr[7];

	long b;//8B
  long brr[7];
};

填充满一个缓存行64字节,此时a,b在不同缓存行中,访问命中无影响

测试:

用时:0.42


总结:

优化程序性能时,合理使用缓存行对于减少缓存未命中和提高数据访问效率非常重要。这可以通过调整数据结构的布局、避免伪共享(False Sharing)等方式来实现

你可能感兴趣的:(杂谈,缓存,jvm)