zoukankan      html  css  js  c++  java
  • 伪共享和缓存行填充

    什么是伪共享

    关于伪共享讲解最清楚的是这篇文章《剖析Disruptor:为什么会这么快?(三)伪共享》,我这里就直接摘抄其对伪共享的解释:

     

    缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如 果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。有 人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。

    为了让可伸缩性与线程数呈线性关系,就必须确保不会有两个线程往同一个变量或缓存行中写。两个线程写同一个变量可以在代码中发现。为了确定互相独立的变量 是否共享了同一个缓存行,就需要了解内存布局,或找个工具告诉我们。Intel VTune就是这样一个分析工具。本文中我将解释Java对象的内存布局以及我们该如何填充缓存行以避免伪共享。

    cache-line.png

    图1说明了伪共享的问题。在核心1上运行的线程想更新变量X,同时核心2上的线程想要更新变量Y。不幸的是,这两个变量在同一个缓存行中。每个线程都要去 竞争缓存行的所有权来更新变量。如果核心1获得了所有权,缓存子系统将会使核心2中对应的缓存行失效。当核心2获得了所有权然后执行更新操作,核心1就要 使自己对应的缓存行失效。这会来来回回的经过L3缓存,大大影响了性能。如果互相竞争的核心位于不同的插槽,就要额外横跨插槽连接,问题可能更加严重。

    JAVA 7下的方案

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public final class FalseSharing implements Runnable { 
        public static int NUM_THREADS = 4; // change 
        public final static long ITERATIONS = 500L * 1000L * 1000L; 
        private final int arrayIndex; 
        private static VolatileLong[] longs; 
       
        public FalseSharing(final int arrayIndex) { 
            this.arrayIndex = arrayIndex; 
        
       
        public static void main(final String[] args) throws Exception { 
            Thread.sleep(10000); 
            System.out.println("starting...."); 
            if (args.length == 1) { 
                NUM_THREADS = Integer.parseInt(args[0]); 
            
       
            longs = new VolatileLong[NUM_THREADS]; 
            for (int i = 0; i < longs.length; i++) { 
                longs[i] = new VolatileLong(); 
            
            final long start = System.nanoTime(); 
            runTest(); 
            System.out.println("duration = " + (System.nanoTime() - start)); 
        
       
        private static void runTest() throws InterruptedException { 
            Thread[] threads = new Thread[NUM_THREADS]; 
            for (int i = 0; i < threads.length; i++) { 
                threads[i] = new Thread(new FalseSharing(i)); 
            
            for (Thread t : threads) { 
                t.start(); 
            
            for (Thread t : threads) { 
                t.join(); 
            
        
       
        public void run() { 
            long i = ITERATIONS + 1
            while (0 != --i) { 
                longs[arrayIndex].value = i; 
            
        
    }
    1
    2
    3
    public class VolatileLongPadding {
        public volatile long p1, p2, p3, p4, p5, p6; // 注释 
    }
    1
    2
    3
    public class VolatileLong extends VolatileLongPadding {
        public volatile long value = 0L; 
    }

     

    把padding放在基类里面,可以避免优化。(这好像没有什么道理好讲的,JAVA7的内存优化算法问题,能绕则绕)。不过,这种办法怎么看都有点烦,借用另外一个博主的话:做个java程序员真难。

     

     

    JAVA 8下的方案

    在JAVA 8中,缓存行填充终于被JAVA原生支持了。JAVA 8中添加了一个@Contended的注解,添加这个的注解,将会在自动进行缓存行填充。以上的例子可以改为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    public final class FalseSharing implements Runnable { 
        public static int NUM_THREADS = 4; // change 
        public final static long ITERATIONS = 500L * 1000L * 1000L; 
        private final int arrayIndex; 
        private static VolatileLong[] longs; 
       
        public FalseSharing(final int arrayIndex) { 
            this.arrayIndex = arrayIndex; 
        
       
        public static void main(final String[] args) throws Exception { 
            Thread.sleep(10000); 
            System.out.println("starting...."); 
            if (args.length == 1) { 
                NUM_THREADS = Integer.parseInt(args[0]); 
            
       
            longs = new VolatileLong[NUM_THREADS]; 
            for (int i = 0; i < longs.length; i++) { 
                longs[i] = new VolatileLong(); 
            
            final long start = System.nanoTime(); 
            runTest(); 
            System.out.println("duration = " + (System.nanoTime() - start)); 
        
       
        private static void runTest() throws InterruptedException { 
            Thread[] threads = new Thread[NUM_THREADS]; 
            for (int i = 0; i < threads.length; i++) { 
                threads[i] = new Thread(new FalseSharing(i)); 
            
            for (Thread t : threads) { 
                t.start(); 
            
            for (Thread t : threads) { 
                t.join(); 
            
        
       
        public void run() { 
            long i = ITERATIONS + 1
            while (0 != --i) { 
                longs[arrayIndex].value = i; 
            
        
    }
    1
    2
    3
    4
    5
    6
    import sun.misc.Contended;
     
    @Contended
    public class VolatileLong {
        public volatile long value = 0L; 
    }

     

    执行时,必须加上虚拟机参数-XX:-RestrictContended,@Contended注释才会生效。很多文章把这个漏掉了,那样的话实际上就没有起作用。


    原文链接:http://www.cnblogs.com/Binhua-Liu/p/5620339.html

  • 相关阅读:
    FusionCharts ScrollColumn2D图
    Java Web项目部署Tomcat运行出错
    Eclipse部署Java Web项目到Tomcat出错
    JavaScript过滤特殊字符
    pl/sql 在一个程序块里打印日志输出到表格
    Java中过滤出字母、数字和中文的正则表达式
    pl/sql 程序块里打印问题
    C++函数的Boost内存池性能介绍
    boost内存池的使用介绍
    内存管理 Boost::singleton_pool
  • 原文地址:https://www.cnblogs.com/wyb628/p/8567693.html
Copyright © 2011-2022 走看看