美文网首页Linux及操作系统网络原理
False share 伪共享-Cache line Paddi

False share 伪共享-Cache line Paddi

作者: chozee | 来源:发表于2020-12-25 11:43 被阅读0次

    False share翻译成伪共享确实有点令人苦恼

    Cache Line Padding
    缓存是以缓存行为最小单位
    一个缓存行可以存储多个不同的数据
    这些不同数据数据被不同线程中的任意一个修改之后,会导致整个缓存行失效,会导致其他线程更新缓存行,这就是低效的原因.

    Cache line Padding技术就是保证每个缓存行只缓存一个数据
    这样虽然使缓存利用变低,但是会减少缓存频繁失效的问题

    cache line (typical) size is 64 bytes
    L1,L2,L3

        Java中一个long8字节,所以一个缓存行可以存8个long类型
    

    缓存行填充具体办法(假设Cache line 64Bytes):
    1. Java8提供了@Contentded注解(java8)进行缓存行填充
    ```
    @sun.misc.Contended

        class MyLong {
    
            volatile long value;
    
        }
    
    
        2. 比如两个long类型之间使用额外的6个long进行填充(具体要填充多少根据实际情况来定)
            Java 程序的对象头固定占 8 字节(32位系统)或 12 字节( 64 位系统默认开启压缩, 不开压缩为 16 字节),所以我们只需要填 6 个无用的长整型补上6*8=48字节,让不同的 VolatileLong 对象处于不同的缓存行,就避免了伪共享( 64 位系统超过缓存行的 64 字节也无所谓,只要保证不同线程不操作同一缓存行就可以)。
    
           
    

    class Pointer {

            volatile long x;
    
            long p1, p2, p3, p4, p5, p6, p7;
    
            volatile long y;
    
        }
    
    
        3. 自定义某些类型达到填充目的
        一旦跨平台,可能一切就无意义了
        
    
        
    
    How to detect false sharing?
     无法从系统层面上通过工具来探测伪共享事件。其次,不同类型的计算机具有不同的微架构(如 32 位系统和 64 位系统的 java 对象所占自己数就不一样),如果设计到跨平台的设计,那就更难以把握了,一个确切的填充方案只适用于一个特定的操作系统。
    
    
    
    Cross core 跨核访问缓存行
        需要Memeory controller支持,总线带宽有限,性能瓶颈
        RFO(Request For Owner) request:跨核访问同一个缓存行当需要写权限就会发送这个请求
        MESI协议
            貌似是intel的一个协议
    
            代表四个状态Modifyied,Exclusive,Shared,Invalid
    
            Exclusive:所有处理的缓存行都没有此行数据,从内存加载到此缓存行后设置成E状态,表示只此一家
    
            Shared:其他处理器有此行数据,则设置为S
    
            Invalid:缓存行没有加载任何数据
    
            Modified:写数据到I状态的缓存行,则置为M状态(处于M态的缓存行,本地处理器继续读写状态不会改变)
    
            远程读写(跨核访问缓存行),如果读就把此缓存行拷贝过去,然后置为S态,如果远程写需要RFO request,此时其他处理器缓存行设为I态,除了自己谁也不许动这行数据,RFO和I态设置会有大消耗
    
    
    
    应用案例
        LinkedBlockingQueue它的last和head经常被不同线程修改,但却可能在同一个缓存行,某些java编译器就会补齐数据
    
        GC可能导致数据在内存和对应的CPU缓存行的位置发生变化所以padding时候要注意
    
        netty和grizzly的代码中的LinkedTransferQueue中都使用了PaddedAtomicReference<QNode>来代替原来的Node, 使用了补齐的办法解决了队列伪共享的问题
    
        ConcurrentHashMap 里面的 size() 方法使用的是分段的思想来构造的,每个段使用的类是 CounterCell,它的类上就有 @sun.misc.Contended 注解。
    
        
    
        
    
    扩展知识
        
    
        计算机的cpu物理核数是同时可以并行的线程数量(cpu只能看到线程,线程是cpu调度分配的最小单位),由于超线程技术,实际上可以并行的线程数量通常是物理核数的两倍,这也是操作系统看到的核数。我们只care可以并行的线程数量,所以之后所说的核数是操作系统看到的核数,所指的核也是超线程技术之后的那个核(不是物理核)。
    
        进程是操作系统资源分配(内存,显卡,磁盘)的最小单位,线程是执行调度(即cpu调度)的最小单位(cpu看到的都是线程而不是进程),一个进程可以有一个或多个线程,线程之间共享进程的资源,通过这样的范式,就可以减少进程的创建和销毁带来的代价,可以让进程少一点,保持相对稳定,不断去调度线程就好。如果计算机有多个cpu核,且计算机中的总的线程数量小于核数,那线程就可以并行运行在不同的核中,如果是单核多线程,那多线程之间就不是并行,而是并发,即为了均衡负载,cpu调度器会不断的在单核上切换不同的线程执行,但是我们说过,一个核只能运行一个线程,所以并发虽然让我们看起来不同线程之间的任务是并行执行的,但是实际上却由于增加了线程切换的开销使得代价更大了。如果是多核多线程,且线程数量大于核数,其中有些线程就会不断切换,并发执行,但实际上最大的并行数量还是当前这个进程中的核的数量,所以盲目增加线程数不仅不会让你的程序更快,反而会给你的程序增加额外的开销。
    
        from https://zhuanlan.zhihu.com/p/82123111
    
        
    
    

    相关文章

      网友评论

        本文标题:False share 伪共享-Cache line Paddi

        本文链接:https://www.haomeiwen.com/subject/vmvbnktx.html