缓存行是数据缓存中的一个固定大小的区域,它的大小通常为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<<"用时:"<<static_cast<double>(end-start)/CLOCKS_PER_SEC<
- getchar();
- }
用时:0.767
解决优化办法就是缓存行填充(也称缓存行对齐)
我们把注释放开,如下:
- struct AB
- {
- long a;//8B
- long arr[7];
-
- long b;//8B
- long brr[7];
- };
填充满一个缓存行64字节,此时a,b在不同缓存行中,访问命中无影响
测试:
用时:0.42
总结:
优化程序性能时,合理使用缓存行对于减少缓存未命中和提高数据访问效率非常重要。这可以通过调整数据结构的布局、避免伪共享(False Sharing)等方式来实现