博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅谈伪共享
阅读量:7112 次
发布时间:2019-06-28

本文共 2659 字,大约阅读时间需要 8 分钟。

在多线程的程序中,我们大部分都是从应用的级别关注共享资源的同步问题,对于底层的资源共享或者冲突问题很少关注,其实伪共享的问题在多线程中是一个效率的隐形杀手,因为很多时候我们从代码中并看不到这个问题的存在。

什么时候产生伪共享

CPU的缓存系统都是以缓存行(cache line)为单位存储的, 缓存行是2的整数幂个连续字节(一般为32~256个字节),最常见的缓存行大小是64个字节。当多线程程序同时修改的两个变量存在在一个缓存行中时,就会引起缓存行级别的互斥,这就是伪共享。

在CPU不同的核中缓存行的同步是通过MESI协议进行同步的,该协议可以保证缓存数据的一致性,当不同核的cache数据不一致的时候就会引起数据的同步问题,同步主要是通过内存进行的,因此对效率的影响是比较大的。

举例说明:

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

避免伪共享

我们可以通过将不同的数据放在不同的缓存行就可以避免伪共享的产生,我们看下在java中是怎么解决的 在JDK6中对于变量的缓存主要是通过增加变量来空间填充进行的,例如:

//ConcurrentHashMapV8.java的实现  public final static class VolatileLong   {     public volatile long value = 0L;     public long p1, p2, p3, p4, p5, p6; // comment out   } 复制代码

对于HotSpot JVM中,在每个对象中还有连个字的对象头(4字节*2),因此上面的CounterCell就可以看出分别是放到了两个缓存行中,只索引是两个缓存行主要是为了适应64位的JVM,这样能保证任何的两个CounterCell都存放在不同的缓存行中。

在JDK8中主要通过注解的方式解决:

@sun.misc.Contended static final class Cell {    volatile long value;    Cell(long x) { value = x; }    ...  }复制代码

性能比较

通过下面的例子真实的模拟下性能的差异

public final class FalseSharing    implements Runnable{    public final static int NUM_THREADS = 4; // change    public final static long ITERATIONS = 500L * 1000L * 1000L;    private final int arrayIndex;     private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];    static {        for (int i = 0; i < longs.length; i++) {            longs[i] = new VolatileLong();        }    }     public FalseSharing(final int arrayIndex) {        this.arrayIndex = arrayIndex;    }     public static void main(final String[] args) throws Exception{        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;        }    }     public final static class VolatileLong{        public volatile long value = 0L;        public long p1, p2, p3, p4, p5, p6; // comment out    }}复制代码

运行上面的代码,增加线程数以及添加/移除缓存行的填充性能对比如下:

从上图能够明显看出伪共享的影响,没有缓存行竞争时,我们几近达到了随着线程数的线性扩展。

上面的文章很多是翻译的Martin Thompson的文章,地址如下: https://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html

转载地址:http://zimhl.baihongyu.com/

你可能感兴趣的文章
简单工厂模式 & 工厂方法模式 & 抽象工厂模式
查看>>
如何关闭visual studio2005实时调试器
查看>>
DotNetBar for Windows Forms 12.2.0.7_冰河之刃重打包版原创发布-带官方示例程序版
查看>>
oracle存储过程
查看>>
HTTP协议详解
查看>>
svn的搭建与使用
查看>>
大型网站技术架构(五)网站高可用架构
查看>>
RabbitMQ学习总结(7)——Spring整合RabbitMQ实例
查看>>
大表改造成分区表
查看>>
Maven学习总结(一)——Maven入门
查看>>
[9-13]Shell系列8——数组
查看>>
Java核心技术之10诀窍 广州疯狂JAVA培训
查看>>
web.xml配置详解
查看>>
Git Tool Part 1
查看>>
Dubbo学习总结(4)——Dubbo基于Zookeeper实现分布式实例
查看>>
程序员会被淘汰吗?
查看>>
【小项目 日程表程序】最近frank我想到一个好项目(好吧,我同意不是我想的)...
查看>>
mysql字符集 乱码问题
查看>>
RabbitMQ学习总结(2)——安装、配置与监控
查看>>
JavaScript学习总结(2)——JavaScript数据类型判断
查看>>