原文地址http://mechanitis.blogspot.com/2011/08/dissecting-disruptor-why-its-so-fast.html

My recent slow-down in posting is because I've been trying to write a post explaining memory barriersand their applicability in the Disruptor. The problem is, no matter how much I read and no matter how many times I ask the ever-patient Martin and Mike questions trying to clarify some point, I just don't intuitively grasp the subject. I guess I don't have the deep background knowledge required to fully understand.

最近我的写作速度慢下来了是因为我正在尝试写一篇解释内存障和它们在Disruptor中适用性的文章。问题是,不管我查看多少资料,也不管我多少次问毫不厌倦的Martin和Mike问题,试图弄清一些观点,但还是不能直观地掌握主旨。估计我没有足够深厚的背景知识来完全理解。

So, rather than make an idiot of myself trying to explain something I don't really get, I'm going to try and cover, at an abstract / massive-simplification level, what I do understand in the area.  Martin has written a post going into memory barriers in some detail, so hopefully I can get away with skimming the subject.

因此比起傻乎乎地尝试解释一些我自己都不明白的东西,我将在抽象的/大量简化的程度上试着涵盖那些我懂的地方。Martin写过一篇有些详细的走进内存障,多希望我可以先去略读一下它。

Disclaimer: any errors in the explanation are completely my own, and no reflection on the implementation of the Disruptor or on the LMAX guys who actually do know about this stuff.

免责声明:讲解中出现的任何错误都完全是我自己的,不反映Disruptor的实现水平或LMAX的那些确实知道这事的伙计们的水平。

What's the point?

重点是什么呢?
My main aim in this series of blog posts is to explain how the Disruptor works and, to a slightly lesser extent, why. In theory I should be able to provide a bridge between the code and the technical paper by talking about it from the point of view of a developer who might want to use it.

我发这一系列博文的主要目的是解释Disruptor如何工作,和较小程度的解释为什么。从理论上说我应该能提供一座代码和技术文档之间的桥梁,通过从想要使用它的开发者的角度来讲解。

The paper mentioned memory barriers, and I wanted to understand what they were, and how they apply.

那个文档提到了内存障,而我想理解它们是什么,并且怎么适用。

What's a Memory Barrier?

内存障是什么?
It's a CPU instruction.  Yes, once again, we're thinking about CPU-level stuff in order to get the performance we need (Martin's famous Mechanical Sympathy).  Basically it's an instruction to a) ensure the order in which certain operations are executed and b) influence visibility of some data (which might be the result of executing some instruction).

它是一个CPU指令。是的,再一次,我们在思考CPU级的东西以便得到我们需要的性能(Martin著名的Mechanical Sympathy)。基本上它是一个指令,为了a)确保特定运算的执行顺序和b)影响一些数据(可能是一些指令的执行结果)的可见性。

Compilers and CPUs can re-order instructions, provided the end result is the same, to try and optimise performance.  Inserting a memory barrier tells the CPU and the compiler that what happened before that command needs to stay before that command, and what happens after needs to stay after.  All similarities to a trip to Vegas are entirely in your own mind.

编译器和CPU能对指令重新排序,来尝试优化性能,最终执行结果是一样的。插入一个内存障会告诉CPU和编译器在那个命令之前执行的需要呆在那个命令之前,在那个命令之后执行的需要呆在那之后。就像一次去拉斯维加斯的旅游全在你脑子里一样。

The other thing a memory barrier does is force an update of the various CPU caches - for example, a write barrier will flush all the data that was written before the barrier out to cache, therefore any other thread that tries to read that data will get the most up-to-date version regardless of which core or which socket it might be executing by.

内存障做的另一件事是强制各种CPU缓存的更新-比如,一个写障会把在这个障之前写到缓存的数据全刷新,于是其他任何线程去读那个数据都会拿到最新的版本,不管它是由哪个内核或socket执行的。

What's this got to do with Java?

这和JAVA有什么关系?
Now I know what you're thinking - this isn't assembler.  It's Java.

我知道你现在在想什么-我们讲的不是汇编。是Java。

The magic incantation here is the word volatile (something I felt was never clearly explained in the Java certification).  If your field is volatile, the Java Memory Model inserts a write barrier instruction after you write to it, and a read barrier instruction before you read from it.

这里神奇的咒语是单词"volatile"(一个我觉得在Java认证中从没明确地解释过的东西)。如果你的字段是volatile的,Java内存模型会在你对它写入之后插入一个写障指令,并且在你对它读取之前插入一个读障指令。

This means if you write to a volatile field, you know that:

这意味着如果你对一个volatile字段写入,你知道的:

  1.Any thread accessing that field after the point at which you wrote to it will get the updated value

     任何在你对这个字段写入之后访问它的线程都会得到更新后的值。

  2.Anything you did before you wrote that field is guaranteed to have happened and any updated data values will also be visible, because the memory barrier flushed all earlier writes to the cache.

     任何你在对这个字段写入之前做的事都被确保发生过了,而任何更新过的数据值都会变得可见,因为内存障把所有早先对缓存的写入都刷新了。

Example please!
请举例!
So glad you asked.  It's about time I started drawing doughnuts again.
高兴你提这个要求。也该是我开始画甜甜圈的时候了。
The  RingBuffer  cursor  is one of these magic volatile thingies, and it's one of the reasons we can get away with implementing the Disruptor without locking.

RingBuffer游标是这些神奇的volatile类型的东西之一,这也是我们可以不用锁而实现Disruptor的原因之一。

The Producer will obtain the next   Entry  (or batch of them) and do whatever it needs to do to the entries, updating them with whatever values it wants to place in there.   As you know, at the end of all the changes the producer calls the commit method on the ring buffer, which updates the sequence number.  This write of the volatile field ( cursor) creates a memory barrier which ultimately brings all the caches up to date (or at least invalidates them accordingly).  
Producer会得到下一个 Entry并对这些条目做它要做的事,把它们更新到任何它想要的值。 如你所知,在所有变更之后生产者会调用ring buffer中的提交方法,这会更新序列号。这个对volatile字段(游标)的写操作创建了一个最终促使所有缓存保持最新的内存障(或者根据情况至少使它们失效)。
At this point, the consumers can get the updated sequence number (8), and because the memory barrier also guarantees the ordering of the instructions that happened before then, the consumers can be confident that all changes the producer did to to the   Entry  at position 7 are also available.
这样的话,消费者们就可以拿到更新过的序列号(8),并且因为内存障同样保证在它之前执行的指令的顺序,消费者们可以确信生产者对7号位 Entry所做的一切变更都是可用的。
...and on the Consumer side?
...那么Consumer那边呢?
The sequence number on the Consumer is volatile, and read by a number of external objects - other downstream  consumers might be tracking this consumer , and the   ProducerBarrier/ RingBuffer(depending on whether you're looking at older or newer code) tracks it to make sure the the ring doesn't wrap.
Consumer中的序列号是volatile类型的,会被一些外部对象读取-其他 下游消费者可能在追踪这个消费者, ProducerBarrier/ RingBuffer(取决于你看的是旧代码还是新代码)追踪它来确保环没有重叠。

So, if your downstream consumer (C2) sees that an earlier consumer (C1) reaches number 12, when C2 reads entries up to 12 from the ring buffer it will get all updates C1 made to the entries before it updated its sequence number.

因此,如果你的下游消费者(C2)看到较早的消费者(C1)到达过12号,当C2从ring buffer读取到12为止的条目的时候在它更新自己的序列号之前它会拿到所有C1所做的更新。

Basically everything that happens after C2 gets the updated sequence number (shown in blue above) must occur after everything C1 did to the ring buffer before updating its sequence number (shown in black).

基本上所有在C2拿到更新过的序列号(上面蓝色表示的)之后发生的事情都必须出现在C1在更新自己的序列号之前对ring buffer做的事情之后。

Impact on performance

对性能的影响
Memory barriers, being another CPU-level instruction, don't have the same cost as locks  - the kernel isn't interfering and arbitrating between multiple threads.  But nothing comes for free.  Memory barriers do have a cost - the compiler/CPU cannot re-order instructions, which could potentially lead to not using the CPU as efficiently as possible, and refreshing the caches obviously has a performance impact.  So don't think that using volatile instead of locking will get you away scot free.

内存障,作为另一个CPU级的指令,没有锁那样的代价-内核没有在多个线程之间干涉和协调。但是没有免费的午餐。内存障也有代价-编译器/CPU不能对指令重新排序,这将隐约导致不能尽可能高效地使用CPU,而且刷新缓存显然也有性能上的影响。因此不要认为用volatile代替锁就能让你逍遥法外。

You'll notice that the Disruptor implementation tries to read from and write to the sequence number as infrequently as possible.  Every read or write of a volatile field is a relatively costly operation. However, recognising this also plays in quite nicely with batching behaviour - if you know you shouldn't read from or write to the sequences too frequently, it makes sense to grab a whole batch of Entries and process them before updating the sequence number, both on the Producer and Consumer side. Here's an example from BatchConsumer:

你会注意到Disruptor的实现尽可能少地对序列号进行读写。每次对volatile字段的读或写都是相对高成本的操作。尽管如此,认识到这在批量的情况也表现得很好-如果你知道的话,你不该对序列号作过多的读写操作,在Producer或Consumer两边抓取一整批Entry并且在更新序列号之前加工它们,都是有意义的。这里有一个来自BatchConsumer的例子:

 
    long nextSequence = sequence +1;
    while(running)
    {
        try
        {
            finallong availableSequence = consumerBarrier.waitFor(nextSequence);
            while(nextSequence <= availableSequence)
            {
                entry = consumerBarrier.getEntry(nextSequence);
                handler.onAvailable(entry);
                nextSequence++;
            }
            handler.onEndOfBatch();
            sequence = entry.getSequence();
        }
        ...
        catch(finalException ex)
        {
            exceptionHandler.handle(ex, entry);
            sequence = entry.getSequence();
            nextSequence = entry.getSequence()+1;
        }
    }

(You'll note this is the "old" code and naming conventions, because this is inline with my previous blog posts, I thought it was slightly less confusing than switching straight to the new conventions).

(你会注意到这是旧代码和约定名称,因为这和我之前发的博文对应,我觉得比起直接切换到新的约定,这样疑惑会更少一些)。

In the code above, we use a local variable to increment during our loop over the entries the consumer is processing.  This means we read from and write to the volatile sequence field (shown in bold) as infrequently as we can get away with.

在上面的代码里,我们在对消费者处理的条目进行循环的时候使用一个局部变量来递增。这表明我们尽可能少地读写那个volatile类型的序列号(粗体的)。

In Summary

总结
Memory barriers are CPU instructions that allow you to make certain assumptions about when data will be visible to other processes.  In Java, you implement them with the volatile keyword.  Using volatile means you don't necessarily have to add locks willy nilly, and will give you performance improvements over using them.  However you need to think a little more carefully about your design, in particular how frequently you use volatile fields, and how frequently you read and write them.

内存障是CPU指令,它们允许你对什么时候数据对其他进程可见作一些假定。在Java中,你通过volatile关键字来实现它们。使用volatile意味着你不管愿不愿意都不必加入锁,并且通过使用它们会给你带来性能上的提升。然而你需要对你的设计想得更仔细一些,特别是你使用volatile字段有多频繁,和对它们读写有多频繁。

PS Given that the New World Order in the Disruptor uses totally different naming conventions now to everything I've blogged about so far, I guess the next post is mapping the old world to the new one.

备注中讲了Disrupor中的”世界新秩序“使用了和我到目前为止发的博文不同的命名约定,我猜下一篇文章应该是将它们做一个对照了。

转载于:https://www.cnblogs.com/adaikiss/archive/2012/02/17/2356082.html

解析Disruptor:解密内存障相关推荐

  1. Carson带你学JVM:图文解析Java虚拟机内存结构

    前言 了解Java中的对象.变量等存放的内存区域十分重要 本文将全面讲解Java虚拟机中的内存模型 & 分区,希望你们会喜欢 Carson带你学JVM系列文章,具体如下: Carson带你学J ...

  2. LevelDB源码解析(1) Arena内存分配器

    你也可以通过我的独立博客 -- www.huliujia.com 获取本篇文章 背景 LevelDB中需要频繁申请和释放内存,如果直接使用系统的new/delete或者malloc/free接口申请和 ...

  3. 一文解析JVM的内存结构,身为程序员还不弄懂JVM怎么行

    欢迎关注专栏:Java架构技术进阶.里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦.微信公众号:慕容千语的架构笔记.欢迎关注一起进步. 前言 Jvm的内存结构是由<jav ...

  4. 高性能并发队列Disruptor使用详解,详细解析Disruptor框架的应用和基本原理

    基本概念 Disruptor是一个高性能的异步处理框架,是一个轻量的Java消息服务JMS, 能够在无锁的情况下实现队列的并发操作 Disruptor使用环形数组实现了类似队列的功能,并且是一个有界队 ...

  5. 深度解析Python的内存管理机制:垃圾回收机制

    Python程序在运行时,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量,计算完成后,再将结果输出到永久性存储器中.但是当数据量过大,或者内存空间管理不善,就很容易出现内存溢出的情况,程序可 ...

  6. 解析Disruptor:写入ring buffer

    原文地址http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-writing-to-ring.html 这是Disruptor end ...

  7. 自己动手写操作系统第三章pmtest7源码解析——检测系统内存

    摘要:在pmtest6.asm中,我们已经初步接触了分页机制,但是很显然,上述分页机制浪费比较严重,而且没有体现应有的用处.本节,我们主要介绍如何根据内存容量,恰当地分配页表. 一.理论基础 1.如何 ...

  8. 【C语言】全面解析数据在内存中的存储

    文章目录 前言 类型的基本分类 整型 浮点数 自定义类型 整型在内存中的存储 原码.反码.补码 大端和小端 如何判断编译器是大端还是小端 浮点数在内存中的存储 总结 前言 C语言中有char.shor ...

  9. 游戏外挂原理解析与制作 - [内存数值修改类 篇四]

    前三篇的博文结合了C#的Demo对内存数据修改一类的挂剖析了原理,利用C#语言调用Windows API,我们其实已经写出了一个简单的内存扫描工具,但是它存在一些缺陷,比如说只能所搜索单一类型数值(整 ...

最新文章

  1. MF训练套件(1):如何去除应用标题?
  2. 解决incorrect 'only available in ES6' warning (W119) with options `moz: true, esversion: 6` 报错问题...
  3. 下巴痤疮的治疗方法有哪些?
  4. Python Django jsonpickle序列化部分字段
  5. c++------------提取文件中的信息
  6. 判断图有无环_【转】判断一个图是否有环 无向图 有向图
  7. m_map投影_MATLAB使用m_map工具箱演示绘制m_map logo
  8. 《数据结构上机实验(C语言实现)》笔记(2 / 12):线性表
  9. SVM与感知机的异同点
  10. windows安装mysql 5.7
  11. 为什么持续集成和部署在开发中非常重要?
  12. Redisson + Lettuce实现
  13. 将Excel表格数据导入SQL表格
  14. 文件mime类型大全
  15. NAO机器人姿势切换
  16. 刚构桥的优缺点_桥梁钢结构特点及优缺点
  17. Ubuntu18.04系统硬盘分区方法
  18. 时空复杂度之珠心算测验
  19. 如何计算词语的相似性(附github)
  20. 关于App置灰黑白化的探索

热门文章

  1. 高德地图通过基站信息获取经纬度和具体位置
  2. 老子的软件之道 - 道篇 18 标准之上
  3. Java【String类】的详解
  4. 相关性分析、相关系数矩阵热力图
  5. iOS启动优化之——如何使用MetricKit 来计算启动时间 Launch Time
  6. 7月1日电动车头盔和充电器新国标正式实施了!
  7. 云丁科技获6亿D轮融资:百度领投 顺为SIG险峰跟投
  8. 在web上呈现3D的语言VRML
  9. 【The 2021 CCPC Guilin】G - Occupy the Cities
  10. ftp服务器怎么保存文件,FTP服务器保存文件