以前,C 和 C++ 是低延迟环境事实上的选择,但现在 Java 使用的越来越多了。
InfoQ 有幸邀请到了这个领域的四位专家,跟他们一起讨论是什么推动了这一趋势,在这种情况下使用 Java 有哪些最佳实践。
与会者名单:
Peter Lawrey是一位对低延迟和高吞吐量系统很有兴趣的 Java 顾问。他曾为多家对冲基金、交易公司和投资银行提供过服务。
Martin Thompson是一位高性能和低延迟方面的专家,具有二十多年的大规模事务处理和大数据系统的工作经验,涉足过汽车、博彩、金融、移动和内容管理等领域。
Todd L. Montgomery是 Informatica Ultra Messaging 的副总架构师,29West 低延迟消息传递产品的首席设计师和实现者。
Dr Andy Piper最近从 Oracle 离职加入到了 Push Technology ,任职首席技术官。
问题列表:
- 我们如何理解低延迟?它和实时一样吗?它与高性能代码一般有怎样的联系?
- 在其他情况下使用 Java 往往会提到以下优势:可以使用丰富的类库、框架和应用服务器等等,而且还有大量掌握它的程序员用户群。在编写低延迟代码时还有这些优势吗?如果没有,那与 C++ 相比 Java 有哪些优势?
- JVM 是如何支持并发程序的?
- 如果先不谈垃圾回收,那么 Java 还有哪些其他特有的技术有助于编写低延迟代码的技术(在 C++ 中不会用到的技术)?我能想到的有 JVM 预热,把所有类加载到 permgen 中以避免 IO,用于避免缓存未命中的 Java 专有技术等等。
- 管理垃圾回收行为对大家用 Java 编写低延迟代码的方式有怎样的影响?
- 在分析低延迟应用时,你有没有在性能的“峰值”和极端值背后发现任何常见原因或模式?
- Java 7 已经开始支持基于 InfiniBand 设备的套接字直接协议(Sockets Direct Protocol,SDP)了。你是否已经看到有生产系统使用过它了?如果这项技术还没有被应用过,那么你看到有什么其他的解决方案吗?
- 下面的问题并不局限于 Java,为什么我们需要尽量避免竞争?当我们无法避免竞争时如何能够更好地管理它?
- 在过去的几年里,你们有没有为了用 Java 做低延迟开发而做出过改变?
- Java 是否适用于其他对性能比较敏感的工作?你会把它用于高频交易(HFT)系统吗?能否举个例子?或者说 C++ 仍然是更好的选择?
问题 1.InfoQ:我们如何理解低延迟?它和实时一样吗?它与高性能代码一般有怎样的联系?
Lawrey:对延迟有严格要求的系统,延迟的时间甚至快到人们根本就看不到。这种延迟时间仅在 100 纳秒到 100 毫秒之间。
Montgomery:实时和低延迟完全不同。大多数人对于“实时”的观点是确定性,即严格控制(甚至界定)峰值的纯粹的速度。然而,通常“低延迟”意味着要最大限度地追求纯粹的速度,与此同时可以容忍个别轻微的偏差。当思考硬实时系统时,这一点是肯定的。低延迟的一个先决条件就是要始终对效率保持足够的敏锐。从系统的角度看,这种效率必须渗透到全部应用栈、操作系统和网络中。这意味着低延迟系统必须让所有其他组件都能达到机械和应的程度。另外,最近几年在低延迟系统中涌现了许多技术,它们源自于操作系统、编程语言、虚拟机、协议、其他系统开发领域乃至硬件设计的高性能技术。
Thompson:性能无非就是两点——吞吐率(比如单位 / 秒)和响应时间(有时也称为等待时间)。最重要的是给出量化的指标,而不能只是说它应该“很快”。实时有非常明确的定义,但却经常被误用。实时与具体系统相关,这些系统在不考虑系统负载的情况下对输入事件到其响应有实际的时间限制。在硬实时系统中如果不能满足这些限制,那么整个系统就会出现故障。大家可以想一下心脏起博器或导弹控制系统,它们能帮我们更好地理解这一概念。
对于交易系统来说,实时系统更倾向于另外一种含义,那就是系统必须拥有高吞吐率并且尽快响应每个事件,这可以理解为“低延迟”。但如果有一次交易没有被及时处理并不代表整个系统发生了错误,所以严格意义上你不能把它称为实时。
好的交易系统要有高质量的执行,其中一个方面就是要有低延迟的响应,响应时间只能有很小的偏差。
Piper:简单来说延迟就是决策与行动之间的延时。在高性能计算环境下,低延迟通常意味着网络间的传输有很低的延迟时间或请求到响应间有很低的整体延迟时间。“低”的定义取决于具体环境,在互联网中低延迟可能是指在 200 毫秒以内,但是在交易应用中可能就是在 2 微秒之内了。从学术上来说低延迟与实时是有区别的,低延迟通常用百分比来度量,度量那些必须要掌握的异常值(未达到低延迟的情况)。而如果是实时的话,你就必须保证系统行为在最大延时之内响应,而不再是度量延迟百分比。你会发现实时系统很容易做成低延迟系统,但反过来就很难了。但是现在,人们渐渐地已经不再严格区分这些概念了,会混着用这些术语。
假设延迟是从请求到响应的整体延迟时间,那么很明显延迟会受到以下诸多方面的影响:CPU、网络、操作系统、应用甚至物理定律。
问题 2.InfoQ:在其他情况下使用 Java 往往会提到以下优势:可以使用丰富的类库、框架和应用服务器等等,而且还有大量掌握它的程序员用户群。在编写低延迟代码时还有这些优势吗?如果没有,那与 C++ 相比 Java 有哪些优势?
Lawrey:如果你的应用把 90% 的时间花在了 10% 的代码上,那么 Java 很难去优化那 10% 的代码,但却很容易编写和维护剩下的那 90% 的代码,尤其是当团队能力水平参差不齐的时候。
Montgomery:在资本市场(特别是算法交易)中有很多可以发挥作用的因素。更快的算法投入市场时往往拥有更多的优势。许多算法都有搁置期,尽快进入市场是充分发挥其优势的关键。与 C 或 C++ 截然不同,Java 有更多社区和可用的选择,这无疑是它强大的竞争优势。虽然有时候纯理论上的低延迟可以不考虑其他关注点。但我想目前 Java 和 C++ 的性能差异并不大,单纯从速度上讲并没有那么黑白分明。通过对垃圾回收技术、运行期编译执行技术优化与运行期的管理的改进,Java 在一向比较薄弱的性能方面增加了一些非常令人赞叹的优势,这些优势不会轻易被人们忽视的。
Thompson:我们用 Java 编写低延迟系统时很少使用第三方甚至标准类库,主要有以下两个原因:首先,许多类库在编写时并没有专门考虑过性能,所以通常达不到令人满意的吞吐率和响应时间。其次,它们通常会用锁来控制并发,这样就会产成大量的垃圾,当锁竞争和垃圾回收时响应时间就会有很大的变数。
Java 有几个最好的工具,这些工具可以支持任何语言,它们能非常有效地提升生产率。上市时间往往是构建交易系统时的一项关键要求,Java 通常在这时总能占得先机。
Piper:很多方面都反过来了,以前很难用 Java 写好的低延迟代码,因为 JVM 把开发人员与硬件隔离开。这是一个非常好的转变,不仅让 JVM 更快、更容易预测了,而且现在开发人员充分理解了 Java 的工作机制(特别是 Java 内存模型)以及它与底层硬件之间的映射方式(Java 可以称得上是第一个为程序员提供了全面的内存模型的流行语言了,C++ 也是在它之后才提供的),从而能够充分地发挥硬件的优势了。比如无锁、无等待技术都是比较好的例子,Martin Thompson 和我们公司(Push)一直在推进这些技术的应用,在我们自己的开发中已经非常成功地应用了这些技术。此外,由于这些技术越来越流行了,我们发现它们正被引入到标准库中(例如 Disruptor ),所以开发人员在使用这些技术时已经无需再详细了解那些底层的行为了。
即使我们抛开这些技术不谈,Java 的安全优势(内存管理、线程管理等)往往也比 C++ 感觉上的性能优势更具价值,而且 JVM 供应商前段时间还声称主流的 JVM 通常比定制的 C++ 代码更快,因为它们可以进行跨应用的整体优化。
问题 3.InfoQ:JVM 是如何支持并发程序的?
Lawrey:Java 从一开始就内置了对多线程的支持,高并发支持标准已经有 10 年了。
Montgomery:JVM 是一个很好的并发程序平台。它的内存模型使开发人员可以在硬件抽象层上以统一的模式使用无锁技术,这个优点能让应用尽可能地发挥硬件的能力。无锁和无等待技术非常适合创建高效的数据结构,这正是开发社区中大家所急需的东西。此外,有一些用于并发的标准类库的结构非常易于使用,使应用可以更具弹性。不仅是 Java 使用了很多这样的结构,还有 C++11(如果抛开某些细节不谈的话)。C++11 的内存模型对于开发人员来说是一次非常巨大的进步。
Thompson:Java(1.5)是第一个拥有详细定义的内存模型的重要语言。有了语言层的内存模型,就可以使开发人员在硬件抽象层上推理并发代码了。这一点至关重要,因为硬件和编译器将激进地重排我们的代码,这存在跨线程可见性问题。你可以使用 Java 编写很好的无锁算法,它能在低并可预估的延迟上实现非常惊人的吞吐量。Java 对锁也有很多的支持。然而,锁竞争时操作系统必须作为一个仲裁者介入,会消耗巨大的性能成本。有没有锁竞争会产生不同的延迟,通常有 3 个数量级的差异。
Piper:Java 从自身的 Java 语言规范开始就已经支持并发编程了——JLS 描述了许多支持并发的 Java 基本实体和结构。在基层是
java.lang.Thread 类,它用来创建和管理线程,关键字
synchronized 用来协调不同线程对共享资源的访问。除此之外,Java 还提供了完整的数据结构包(java.util.concurrent
),从并发哈希锁到任务调度程序再到不同的锁类型,该包都已经针对并发编程进行了优化。Java 内存模型(JMM)是其中最大的一项支持,它是作为 JDK5 中的一部分并入到了 JLS 中。它确保了开发人员在处理多线程及其行为时可以有相应的预期。有了它之后开发人员就可以更容易地编写出高性能、线程安全的代码了。在开发 Diffusion 时,为了实现最佳的性能我们非常依赖于 Java 内存模型。
问题 4.InfoQ:如果先不谈垃圾回收,那么 Java 还有哪些其他特有的技术有助于编写低延迟代码的技术(如果你用 C++ 就不会用到这些技术)?我能想到的有 JVM 预热,把所有类加载到 permgen 中以避免 IO,用于避免缓存未命中的 Java 专有技术等等。
Lawrey:Java 能让你编写、测试和剖析应用程序,使应用在有限的资源内更加有效。这使你能有更多的时间确保完成所有最重要的事。我见过很多 C 或 C++ 项目花了很多的时间去深度探讨底层,最终两个终端之间仍会很长时间的延迟。
Montgomery:这个问题可有些难度。有一点比较明显,JVM 的预热可以做适当的优化。然而,目前的 C++ 无法在运行期做类层次分析时优化一些类和方法的调用。在 C++ 中可以使用很多其他技术,或者在某些情况下并不需要这些技术。不论哪种语言的低延迟技术通常都有一些建议,告诉你最好不要去做哪些事,它们会带来很大的影响。在 Java 中需要避免的对低延迟应用有不良影响的做法不算很多。其中之一是不要使用特定的 API,比如 Reflection(反射)API。非常幸运的是,我们通常可以用更好的方案达成相同的结果。
Thompson:答案就在你的问题里,:-)。从本质上说,Java 必须先预热才能使运行期达到稳定状态。一旦稳定下来 Java 就可以像本机语言一样快了,甚至在某些情况下会更快。Java 最大的弱点是缺少对内存层的控制。在主流处理器中一次缓存未命中就会丢失 500 条已经执行过的指令。为了避免缓存不能命中,我们需要控制内存层,以一种可预测的方式访问内存。为了达到这种程度的控制,并减轻垃圾回收的压力,我们通常要用 DirectByteBuffers 去创建数据结构,或者放弃堆而使用 Unsafe。这就可以做到精确的数据结构规划。如果 Java 引入对结构体数组的支持,就不必再这么做了。这并不需要切换语言,只是引入一些新的特性。
Piper:这个问题似乎是一个伪命题。综合所有情况来看,编写低延迟程序还是编写其他特别关注性能的程序是非常类似的(无论 C++ 还是 Java),无非是让开发人员编写的代码在某些层进行间接处理后(比如,通过 JVM 或者通过 C++ 的类库、编译优化等)在硬件平台上运行,事实上这些细节的不同并不会造成多大的差异。优化本质上是一种演练,优化的规则一直都是像下面这样的描述:
- 不要。
- 也不要(只适用于专家)。
如果没达到你想要优化到的程度:
- 看看你是否真的需要提速。
- 剖析代码看看实际上把时间都花在了哪里。
- 重点关注具有高回报值的区域,先不考虑其他部分。
当然现在你用工具做这些事的时候,Java 和 C++ 可能有不同的潜在热点,那只是因为它们本来就不同嘛。诚然,你需要比普通的 JAVA 程序员多了解一些细节(使用 C++ 也是如此);相比而言使用 Java 时某些内容并不需要做过多深入地了解,因为在运行期已经对它们进行了充分地处理。你可能需要优化以下几个方面:可疑的代码路径、数据结构和锁。我们在 Diffusion 中采用了基准驱动法,我们不断地剖析我们的应用程序,寻找优化的可能性。
问题 5.InfoQ:管理垃圾回收行为对大家用 Java 编写低延迟代码的方式有怎样的影响?
Lawrey:在不同的情况下有不同的解决方案。我首选的解决方案是把垃圾限制到最小,那么它就不会造成多大危害了。你可以把垃圾回收的次数降低到每天一次以内。
个人认为,这时候减少垃圾回收真正的理由是擦除尚未填满 CPU 缓存的垃圾。减少这些垃圾能为你提升 2 到 5 倍的代码性能。
Montgomery:我发现大多数 Java 低延迟系统为了最小化甚至试图消除垃圾的产生已经做出了最大的努力。比方说,避免使用字符串都不算是什么稀罕事了。Informatica Ultra Messaging (UM) 自己已经提供了特定的 Java 方法以迎合大量用户复用对象的需求,并避免了一些使用模式。如果让我来猜的话,最常见的隐含式已经成为对象复用的流行用法。这一模式也受到了其他许多非低延迟类库的影响,比如 Hadoop。它目前已经成为社区内的一项常用技术,它为 API 或框架用户提供了选项和方法,使用户可以在低垃圾或者零垃圾的方式下使用它们。
除了代码实践方面的影响,运维也会对低延迟系统产生影响。正像我们说过的,许多系统将采取一些垃圾回收的创新。把垃圾回收的执行限制在每天特定的时间已经不再是一种罕见的方法。这意味着应用设计和运维需求是控制异常值和获得更多确定性的主要因素。
Thompson:如果使用对象池(像前面的回答中所说的)就需要在 ByteBuffers 或空闲的堆中管理大部分数据结构,这使 Java 程序有了 C 一样的风格。如果我们拥有真正的并发垃圾回收器就可以避免这种结果。
Piper:java.lang.String 到底会有多大?不好意思,开个玩笑,实话实说,与其让每个程序员去修改他们的代码,还不如改进垃圾回收的行为。以 HotSpot 为例,从早期垃圾回收停顿以分钟计,到现在也走过了一段艰难漫长之路。许多改进都是由市场竞争驱动的,从延迟的角度来看,BEA JRockit 在过去的表现一向都比 HotSpot 要好很多,抖动要低得多。然而最近 Oracle 正在合并 JRockit 和 HotSpot 的代码库,原因恰恰是它们之间几乎没有多大差距了。在其他很流行的 JVM(比如 Azul 的 Zing)上也可以看到很多类似的改进,许多情况下开发人员试图“改进”垃圾回收的行为,但没有取得真正的效果,反而有的时候适得其反。
但是,这并不是说开发人员不能去管理垃圾回收,举例来说,合并和使用空闲的堆存储可以降低对象的分配,从而限制内存抖动。同时也应该想到,JVM 的开发人员也非常关心这些问题,所以你基本没必要自己去处理这些事,或者只需要购买一个商业的 JVM。最糟糕的是,你在这一方面优化应用的时候根本就不确定它是不是真的有问题,但从此以后由于这类技术绕过了 Java 垃圾回收那些非常实用的特性,从而增加了应用的复杂度,使之后的维护更加困难。
问题 6.InfoQ:在分析低延迟应用时,你有没有在性能的“峰值”和极端值背后发现任何常见原因或模式?
Lawrey:IO 等待之类的。CPU 指令或数据缓存干扰。上下文切换。
Montgomery:在 Java 里,大家开始更加充分地理解垃圾回收停顿了,我们很幸运能够用到更好的垃圾回收器,它更为实用。然而,系统影响对于所有语言都是共有的。在峰值背后的众多原因之中,躲藏着这么一个原因,那就是操作系统调度延迟。有时它是直接的延迟,而有时它是由于延迟引起的连锁反应,相比而言这更要命。在重负载之下某些操作系统的调度要比其他操作系统更好。出人意料的是,对于许多开发人员来说,糟糕的应用选择会经常使调度成为意外状况,而且往往难以充分调试。你需要注意来自于 I/O 的内在延迟和在某些系统上会发生的 I/O 竞争。一个好的假设是,任何 I/O 调用都可能会在某些点和将在某些点上阻塞。往往关键是思考其内在的影响。请铭记,网络调用即 I/O。
还有许多网络方面的特定原因同样会造成糟糕的性能。我列举一下几条比较关键的原因。
- 网络通行要花时间。在广域网环境里,跨网间传播数据需要花费大量的时间。
- 以太网是非可靠的,它是提供可靠性之上的协议。
- 网内丢包引起延迟,这些延迟是由于中继和恢复以及类似于 TCP 队头阻塞的次级效应。
- 使用 UDP 时,由于资源匮乏造成在接收端发生各种方式的网内丢包。
- 由于交换机和路由器拥塞发生网内丢包。路由器和交换机是很自然的竞争点,它们之间产生竞争时丢包是权益之计。
- 可靠的网络介质(比如 InfiniBand)针对网络层的延迟在权衡之下会选择丢包。不过,丢包的最终结果同样还是会造成延迟。
在很大程度上,大量使用网络的低延迟应用往往不得不考虑大量的延迟原因以及附加的网内抖动的来源。在很多低延迟应用中,抖动的最常见原因除了网络延迟外,有最大嫌疑的可能就是丢包了。
Thompson:我清楚很多延迟峰值的原因。许多人都知道垃圾回收,除此之外我还清楚许多锁竞争、TCP 相关的问题以及许多 Linux 内核由于配置不当导致的相关问题。许多应用的算法设计得比较差,它们在突发情况下无法分摊那些开销很大的操作(比如 IO 和缓存未命中),从而形成排队效应。我发现算法设计常常是应用性能问题和延迟峰值的最主要原因。
处理延迟峰值时,到达安全点的时间(TTS)是一个主要考虑因素。许多 JVM 操作需要通过使所有用户线程达到安全点才能中止这些线程。安全点检查通常是在方法返回上执行的。这需要安全点能够成为来自于撤销的倾向锁、某些 JNI 交互、未优化的代码直到许多垃圾回收阶段的任何事物。通常把所有线程发到安全点所花的时间比完成作业本身还要多得多。随后的工作是要花费巨大的成本去唤醒那些所有的线程让它们再次执行。让一个线程快速、可预见地到达安全点通常不是许多 JVM 考虑或优化的部分,比如对象克隆和数组复制。
**Piper:峰值最常见的原因是垃圾回收停顿,改善垃圾回收停顿最常用的方法是垃圾回收调优,这要优于实际去变更代码。例如,JDK6 和 JDK7 默认使用的是并行收集器(**parallel collector),我们只是简单地把它换为并发标记清除收集器(concurrent mark sweep collector)就能使“全世界停止运行”的垃圾回收停顿产生巨大的变化,通常正是它导致的峰值。除此之外,你还要考虑用到的堆的大小。太大的堆会给垃圾回收带来更大的压力,会造成更长的停顿时间,一般情况下只需简单地消除内存泄漏和降低内存使用率就会使一个低延迟应用有截然不同的整体行为表现。
除了垃圾回收之外,延迟峰值的另一主要原因是锁竞争,但由于它通常难以确定,所以使它更加难以识别和处理。另外一定要牢记的是,任何时候应用都没有能力去处理它,它将产生延迟峰值。很多情况下都会产生锁竞争,有一些甚至在 JVM 控制之外,比如访问内核或操作系统资源。如果可以识别出这些限制,那么完全可以修改应用使它不使用这些资源,或者改变使用这些资源的时间。
问题 7.InfoQ:Java 7 已经开始支持基于 InfiniBand 设备的套接字直接协议(Sockets Direct Protocol,SDP)了。你是否已经看到过有生产系统使用过它了?如果这项技术还没有被应用过,那么你看到有什么其他的解决方案吗?
Lawrey:因为它会产生相当多的垃圾,所以我没有把它用于以太网。在低延迟系统中,你希望把网络跳数降到最低,通常唯一不能移除的就是外部连接。这些通常都是以太网。
Montgomery:我们见过的不多。在前面我提到过它,但我们还没有看到它被认真考虑过。Ultra Messaging 是用来在 SDP 和开发人员之间使用消息传递的接口。SDP 非常适合用于®DMA 访问模式,而不是基于推送的应用模式。虽然可以把 DMA 模式转变为推送模式,但很遗憾,这不适合用 SDP 来做。
Thompson:我还没有见过它在实际环境中的应用。多数人使用类似于 OpenOnload 栈和那些来自于 Solarflare 或 Mellanox 之类的网络适配器。在极端情况下,我看到在 InfiniBand 上的 RDMA 使用预定义的锁无关算法直接从 Java 中访问共享的内存。
Piper:Oracle 的 Exalogic 和 Coherence 这两个产品已经用过 Java 和 SDP 有一段时间了,所以从这个意义上来说,我们已经见过这一特性在产品系统中应用过一段时间了。按照开发人员实际使用 Java SDP 代替某些第三方产品去支持目录的情况看,也没有太多,但是如果他能增加商业利益,那我们预计这一点会有所改变。我们自己已经使用了针对延迟优化过的硬件(例如 Solarflare10GbE 适配器),从核心驱动安装包中获得的好处要优于具体的 Java 调优。
问题 8.InfoQ:下面的问题并不局限于 Java,为什么我们需要尽量避免竞争?当我们无法避免竞争时如何能够更好地管理它?
Lawrey:对于追求极致的低延迟来说,这的确是个问题,但对于几微秒级的延迟这就不是个问题了。如果你无法避免这种情况,就要尽量把它的影响降到最低吧。
Montgomery:竞争总会发生的。对它的管理至关重要。处理竞争的最佳方法之一就是架构。“单一写原则”是一种有效的方法。其实,假设有一具单独的写入器,并围绕这一基本原则构建的话,那么就不会有竞争了。使单一的写的工作降到最低,你们会为完成的效果感到惊奇的。
异步行为是一个能够避免竞争的很好的方法。它总是围绕着这样的一个原则:“永远只做有用的工作”。
这通常也会变成单一写原则。我通常喜欢在竞争资源的单一写入器前放一个锁无关队列,用线程执行所有的写操作。线程什么都不做,它只是把写操作推入到队列中,然后这些写操作会在循环中执行。这么做非常有利于批处理。队列方面,一种无等待的方法在此有极大的帮助,从调用者的视角来看,就是在这里执行的异步行为。
Thompson:一旦我们的算法中有了竞争,我们就会有一个基本成正比的瓶颈。竞争点形成队列,利特尔法则( Little’s Law )开始起作用了。我们还可以使用阿姆达尔定律( Amdahl’s Law )模拟竞争点的时序约束。大多数算法可以被重写以避免来自多线程或执行环境给定的并行加速(通常通过管道)的竞争。如果我们真的必须管理指定数据资源的竞争,那么处理器提供的原子指令往往是比锁更好的解决方案,因为它们是在永远不会涉及内核的用户空间运转的。新一代英特尔处理器(Haswell)扩展了这些指令,使硬件事务型内存支持数据原子性的少量更新。但很遗憾的是,Java 很可能需要花上一段时间才能为程序员直接提供这种支持。
Piper:对于低延迟应用来说,锁竞争可能是最大的一个性能障碍了。锁本身并没有多少性能开销,在无竞争情况下 Java 的 _ 同步 _ 锁也可以执行地非常好。然而,有竞争时锁的性能就会一落千丈了,不仅仅因为一个线程持有锁使其他线程拿不到同一个锁,还因为更多线程访问这个锁时会给 JVM 带来昂贵的锁管理成本,这个道理很容易理解。很明显,关键是要避免锁竞争,所以不要同步那些不需要同步的东西(比如,移除那些什么都不保护的锁、缩小锁的范围、降低锁持有的时间、不要混淆锁的职责等等)。另一种常用的技术是消除多线程访问,不要让多个线程去访问一个共享的数据结构;你可以把更新操作当成命令排成队列,这样就变成了单线程处理。这样就把锁竞争简单地归结成了添加在队列中的一个项目了,而它可以通过锁无关技术来实现自我的管理。
问题 9.InfoQ:在过去的几年里,你们有没有为了用 Java 做低延迟开发而做出过改变?
Lawrey:构建一个简单的系统,让它只做你想让它做的事情。尽可能地对它进行端到端的调优。优化和(或)重写那些你测量出瓶颈的地方。
Montgomery:翻天覆地地变化。Ultra Messaging 创建于 2004 年。在那个时候,想把 Java 用于低延迟可不是一个很明智的选择。除了极少的几个人确实考虑过它。后来人越来越多。我想现在这种局面已经被彻底扭转了。Java 不仅是可行的,甚至可能成为低延迟系统的主要选择。Martin Thompson 和 [Azul Systems’] Gil Tene 完成了这项伟大的工程,他们真正地推动了社区对此的态度转变。
Thompson:最近几年的主要变化是持续完善锁无关和缓存友好算法。我喜欢经常参加一些与语言相关的论战,在这些论战中抛出观点来证明在性能方面算法比语言更加重要。不管是什么语言,干净的代码(它展示了机器合应)更容易带来优异的性能。
Piper:Java 虚拟机和硬件正在不断地改进,低延迟开发永远都是一场军备竞赛, 为的就是能够保持在目标架构的最佳位置。JVM 的 Java 内存模型和并行数据结构(这依赖于底层硬件的支持)的实现也更加强壮、可靠了,所以像那些锁无关、无等待技术也已经成为主流。硬件现在的发展方向也是在越来越多的执行内核的基础上追求越来越多的并发,所以那些充分发挥这些变化的优势技术,以及那些尽可能降低冲击(比如给避免锁竞争增加更多的权重)的技术正在成为开发环节的要点。
在 Diffusion,我们现在已经在标准版的 Intel 硬件上用标准版的 JVM 把延迟时间降到了 10 微秒之内。
问题 10.InfoQ:Java 是否适用于其他对性能比较敏感的工作?你会把它用于高频交易(HFT)系统吗?能否举个例子?或者说 C++ 仍然是更好的选择?
Lawrey:以上市时间的角度来说(团队的可维护性和支持的综合能力),我认为 Java 是最好的。C 或 C++ 在你要使用 Java 和 FPGA(或 GPU)之间的空间一直在不断地缩小。
Montgomery:对于大多数高性能工作来说,使用 Java 肯定都会是一个明智的选择。对于高频交易(HFT)来说,Java 几乎已经有了所需的一切。它有更加广阔的发挥空间,尽管最明显的是有了更多的内联函数。我认为,Java 在其他领域可以做得很好。就像低延迟那样,我认为它会让开发人员去乐意尝试也同样做到的。
Thompson:如果有绝对充足的时间,我就能让 C(或 C++ 或 ASM)的程序性能比 Java 更好,但现在我没有那么长的时间。通常 Java 是一种非常快速的交付方式。如果 Java 有好的并发垃圾回收器、内存层的控制、无符号类型以及一些访问 SIMD 和并发基元的内联函数,那我可要变成一只非常快乐的兔子喽。
Piper:我把 C++ 看作是一种最优化的选择。从上市时间、可靠性、高质量的角度来看,Java 是迄今为止首选的开发环境,所以我常把 Java 作为第一选择,除非确实有 Java 无法解决的瓶颈,否则不考虑换其他的语言,这已经成了我的口头禅。
专题讨论小组成员简介
Peter Lawrey是一名对低延迟和高吞吐率系统很感兴趣的 Java 咨询师。他曾为多家对冲基金、交易公司和投资银行提供过服务。Peter 在 StackOverflow 上的 Java 方面排在第 3 名,他的技术博客每月有 12 万的页面浏览数,他是 github 上 OpenHFT 项目最重要的开发人员。OpenHFT 项目包括有 Chronicle,它每秒最多支持 1 亿的持久化消息。Peter 每月会到性能 Java 用户组就不同的低延迟主题做两次免费的讲习会。
Todd L. Montgomery是 29West 的 Messaging Business Unit(现在已经隶属于 Informatica)的副总架构师。Todd 作为 Informatica 的 Messaging Business Unit 的总架构师负责 Ultra Messaging 产品系列的设计与实现,该产品系列已经有超过 170 多个产品在金融服务业内得到了有效地应用。在过去,Todd 曾负责过 TIBCO 和 Talarian 的架构工作,还负责过 West Virginia University 的研究和演讲,曾为 IETF 做出过贡献,完成 NASA 在各个软件领域的研究工作。在消息平台、可靠的多路广播、网络安全、拥塞控制和软件质保等方面均有较深的资历,他以 20 年的实战开发经验为我们带来了一种独特的视角。
Martin Thompson是一名高性能和低延迟专家,有着超过 20 年的大规模事务处理和大数据领域(包括自动化、博彩、金融、移动和内容管理)的从业经验。他认为机械和应(对硬件的理解,必将有助于软件的创造)是交付优雅的、高性能解决方案的基石。Martin 曾是 LMAX 的联合创始人和首席技术官,直至他离开去专门研究帮助他人使软件达到优越的性能。并发编程框架 Disruptor 是他创造的机械和应的其中一个实例。
Dr Andy Piper近期加入到 Push Technology 的团队中出任首席技术官。Andy 之前是 Oracle Corporation 的技术总监,他在科技前沿有着超过 18 年的工作经验。在 Oracle 的时候,Andy 领导 Oracle Complex Event Processing (OCEP) 的开发,并推进国际化产品策略和创新。在 Oracle 之前,Andy 是 BEA Systems 的 WebLogic Server Core 的架构师,负责中间件基础架构技术。
查看英文原文: Virtual Panel: Using Java in Low Latency Environments
评论