Disruptor的共享与缓存是怎样的

技术Disruptor的共享与缓存是怎样的这篇文章将为大家详细讲解有关Disruptor的共享与缓存是怎样的,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。什么是共享下图是

本文将详细解释如何共享和缓存中断器。文章内容质量很高,我就分享给大家作为参考。希望大家看完这篇文章后对相关知识有一定的了解。

00-1010下图是计算的基本结构。L1、L2、L3分别代表一级缓存、二级缓存和三级缓存。越靠近CPU的缓存,速度越快,容量越小。因此,L1缓存虽小但速度快,而且离使用它的CPU内核很近。L2更大更慢,仍然只能由单个中央处理器内核使用。L3更大、更慢,由单个插槽上的所有CPU内核共享;最后是主存,由所有插槽中的所有CPU内核共享。

Disruptor的共享与缓存是怎样的

图3计算机CPU和缓存示意图。

当中央处理器执行操作时,它首先去L1查找所需的数据,然后去L2,然后去L3。如果这些缓存最终都不可用,所需的数据将进入主内存。你走得越远,计算的时间就越长。因此,如果您非常频繁地执行某项操作,您应该尝试确保数据在L1缓存中。

另外,线程之间共享一条数据时,一个线程需要将数据写回主存,而另一个线程需要访问主存中对应的数据。

以下是从CPU访问不同级别数据的时间概念:

从CPU到所需CPU周期所需的时间约为60-80nsQPI总线传输(套接字之间,未画出),约20nsL3缓存,约40-45周期,约15nsL2缓存,约10周期,约3nsL1缓存,约3-4周期,约1ns寄存器,1周期。可以看出,CPU读取主存中的数据会比L1慢近两个数量级。

什么是共享

缓存由许多缓存行组成。每个高速缓存行通常是64字节,它实际上是指主存储器中的一个地址块。一个Java长变量是8字节,所以一个缓存行可以存储8个长变量。

每次中央处理器从主存储器中提取数据时,它也会将相邻的数据存储到同一高速缓存行中。

当访问长数组时,如果数组中的一个值被加载到缓存中,它将自动加载其他七个值。所以你可以非常快速地遍历这个数组。事实上,您可以非常快速地遍历在连续内存块中分配的任何数据结构。

以下示例测试使用缓存线和不使用缓存线的要素之间的效果比较。

package com . meituan . Falssharing;

/**

*@authorgongming

* @描述

*@date16/6/4

*/

publicclassCacheLineEffect{

//考虑一般缓存行大小为64字节,长类型占用8字节。

static long[][]arr;

publicationstativitmain(String[]args){ 0

arr=new long[1024 * 1024][];

for(inti=0;i1024 * 1024I){ 0

arr[I]=new long[8];

(=NationalBureauofStandards)国家标准局

p;for (int j = 0; j < 8; j++) {
                arr[i][j] = 0L;
            }
        }
        long sum = 0L;
        long marked = System.currentTimeMillis();
        for (int i = 0; i < 1024 * 1024; i+=1) {
            for(int j =0; j< 8;j++){
                sum = arr[i][j];
            }
        }
        System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
 
        marked = System.currentTimeMillis();
        for (int i = 0; i < 8; i+=1) {
            for(int j =0; j< 1024 * 1024;j++){
                sum = arr[j][i];
            }
        }
        System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
    }
}

在2G Hz、2核、8G内存的运行环境中测试,速度差一倍。

结果: Loop times:30ms Loop times:65ms

什么是伪共享

ArrayBlockingQueue有三个成员变量: - takeIndex:需要被取走的元素下标 - putIndex:可被元素插入的位置的下标 - count:队列中元素的数量

这三个变量很容易放到一个缓存行中,但是之间修改没有太多的关联。所以每次修改,都会使之前缓存的数据失效,从而不能完全达到共享的效果。

Disruptor的共享与缓存是怎样的

图4 ArrayBlockingQueue伪共享示意图

如上图所示,当生产者线程put一个元素到ArrayBlockingQueue时,putIndex会修改,从而导致消费者线程的缓存中的缓存行无效,需要从主存中重新读取。

这种无法充分使用缓存行特性的现象,称为伪共享。

对于伪共享,一般的解决方案是,增大数组元素的间隔使得由不同线程存取的元素位于不同的缓存行上,以空间换时间。

package com.meituan.FalseSharing;
 
public class FalseSharing implements Runnable{
        public final static long ITERATIONS = 500L * 1000L * 100L;
        private int arrayIndex = 0;
 
        private static ValuePadding[] longs;
        public FalseSharing(final int arrayIndex) {
            this.arrayIndex = arrayIndex;
        }
 
        public static void main(final String[] args) throws Exception {
            for(int i=1;i<10;i++){
                System.gc();
                final long start = System.currentTimeMillis();
                runTest(i);
                System.out.println("Thread num "+i+" duration = " + (System.currentTimeMillis() - start));
            }
 
        }
 
        private static void runTest(int NUM_THREADS) throws InterruptedException {
            Thread[] threads = new Thread[NUM_THREADS];
            longs = new ValuePadding[NUM_THREADS];
            for (int i = 0; i < longs.length; i++) {
                longs[i] = new ValuePadding();
            }
            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 = 0L;
            }
        }
 
        public final static class ValuePadding {
            protected long p1, p2, p3, p4, p5, p6, p7;
            protected volatile long value = 0L;
            protected long p9, p10, p11, p12, p13, p14;
            protected long p15;
        }
        public final static class ValueNoPadding {
            // protected long p1, p2, p3, p4, p5, p6, p7;
            protected volatile long value = 0L;
            // protected long p9, p10, p11, p12, p13, p14, p15;
        }
}

在2G Hz,2核,8G内存, jdk 1.7.0_45 的运行环境下,使用了共享机制比没有使用共享机制,速度快了4倍左右。

结果: Thread num 1 duration = 447 Thread num 2 duration = 463 Thread num 3 duration = 454 Thread num 4 duration = 464 Thread num 5 duration = 561 Thread num 6 duration = 606 Thread num 7 duration = 684 Thread num 8 duration = 870 Thread num 9 duration = 823

把代码中ValuePadding都替换为ValueNoPadding后的结果: Thread num 1 duration = 446 Thread num 2 duration = 2549 Thread num 3 duration = 2898 Thread num 4 duration = 3931 Thread num 5 duration = 4716 Thread num 6 duration = 5424 Thread num 7 duration = 4868 Thread num 8 duration = 4595 Thread num 9 duration = 4540

备注:在jdk1.8中,有专门的注解@Contended来避免伪共享,更优雅地解决问题。

关于Disruptor的共享与缓存是怎样的就分享到这里了,希望

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/39236.html

(0)

相关推荐

  • JBuilder下调试java程序的示例分析

    技术JBuilder下调试java程序的示例分析JBuilder下调试java程序的示例分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。初学者总问如何

    攻略 2021年12月2日
  • Sharp爬虫程序配置代理ip的示例分析

    技术Sharp爬虫程序配置代理ip的示例分析这期内容当中小编将会给大家带来有关Sharp爬虫程序配置代理ip的示例分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。 // 要访问的目

    攻略 2021年10月28日
  • 如何使用phoenix操作hbase数据)

    技术如何使用phoenix操作hbase数据)这篇文章将为大家详细讲解有关如何使用phoenix操作hbase数据),小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。1.下载安装phoen

    攻略 2021年12月9日
  • linux命令中su和su -有什么区别

    技术linux命令中su和su -有什么区别这篇文章主要介绍了linux命令中su和su -有什么区别,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。简单俩

    攻略 2021年11月20日
  • 如何使用MySQL字符集

    技术如何使用MySQL字符集本篇内容介绍了“如何使用MySQL字符集”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!最近迁

    攻略 2021年10月22日
  • 形容坏人的成语,形容和坏人一伙的的成语

    技术形容坏人的成语,形容和坏人一伙的的成语鱼龙混杂【拼音】形容坏人的成语:yú lóng hùn zá【解释】:比喻坏人和好人混在一起。
    【出处】:唐·张志和《和渔夫词》十三:“风搅长空浪搅风,鱼龙混杂一川中。”
    【示例

    生活 2021年10月29日