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


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

    缓存行,伪共享

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

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

    伪共享问题:

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

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

    1. struct AA
    2. {
    3. long a;//8B
    4. long b;//8B
    5. };

    比如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上去操作数据

    1. struct AB
    2. {
    3. long a;//8B
    4. //long arr[7];
    5. long b;//8B
    6. //long brr[7];
    7. };
    8. AB ab{0,0};
    9. //假设开两个线程,跑到不同的core上
    10. //模拟core1 操作a
    11. void funa()
    12. {
    13. for(int i=0;i<100000000;++i)
    14. {
    15. ab.a++;
    16. }
    17. }
    18. //模拟core2操作b
    19. void funb()
    20. {
    21. for(int i=0;i<100000000;++i)
    22. {
    23. ab.b++;
    24. }
    25. }
    26. int main()
    27. {
    28. clock_t start,end;
    29. start=clock();
    30. thread t1(funa);
    31. thread t2(funb);
    32. t1.join();
    33. t2.join();
    34. end=clock();
    35. cout<<"用时:"<<static_cast<double>(end-start)/CLOCKS_PER_SEC<
    36. getchar();
    37. }

    用时:0.767


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

    我们把注释放开,如下:

    1. struct AB
    2. {
    3. long a;//8B
    4. long arr[7];
    5. long b;//8B
    6. long brr[7];
    7. };

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

    测试:

    用时:0.42


    总结:

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

  • 相关阅读:
    Windows 95 的辉煌诞生历史
    外贸干货/与非洲客户打交道要知道的几点
    【LeetCode】46. 全排列
    如何搭建一个基础的springmvc+mybatis项目
    微分中值定理证明和总结
    代码随想录笔记_哈希_1002查找共用字符
    Ti AM335X工控模块的ubuntu系统定制与使用-连载 (一)
    Golang API框架
    Linux之基础文件类指令(二)
    C# 为什么要限制静态方法的使用
  • 原文地址:https://blog.csdn.net/weixin_51609435/article/details/132355970