BEAM和JVM虚拟机对比:JVM是为并行而构建的,而BEAM是为并发构建的 | Erlang


任何编程语言在Erlang生态系统中的成功都可以分为三个紧密耦合的组件。它们是:

  1. Erlang编程语言的语义,并在其上实现其他语言
  2. 用于构建可伸缩和弹性并发系统的OTP库和中间件
  3. 与语言语义紧密耦合的BEAM虚拟机和OTP。

单独使用这些组件中的任何一个,您将获得亚军。但是,将这三个因素放在一起,您将获得可伸缩,灵活的软实时系统的无可争议的赢家。引用Joe Armstrong的话:“您可以复制Erlang库,但是如果它不能在BEAM上运行,则无法模拟语义”。
在本文中,我们想探索BEAM VM内部。我们将在适用的情况下将它们与JVM进行比较和对比,强调您为什么要注意它们并加以注意。长期以来,此组件一直被视为黑匣子,并且在不了解原因或含义的情况下被视为理所当然。现在该改变这种情况了!

BEAM的亮点
发明Erlang和BEAM VM是解决特定问题的正确工具。它们是由爱立信开发的,旨在帮助实现处理移动和固定网络的电信基础设施。该基础架构本质上是高度并发和可伸缩的。它必须显示软实时属性,并且永远不会失败。BEAM VM通过提供可在可预测的并发编程模型之上运行的微调功能进行了优化,以解决许多挑战。
它的秘诀是轻量级进程,它们不共享内存,由调度程序管理,该调度程序可以跨多个内核管理数百万个进程。它使用基于每个进程运行的垃圾收集器,并对其进行了高度优化以减少对其他进程的影响。结果,垃圾收集器不会影响系统的整体软实时属性。BEAM也是唯一使用规模广泛且具有内置分发模型的VM,它具有内置的分发模型,该模型允许程序透明地在多台计算机上运行。

JVM的亮点
Java虚拟机(JVM)是​​由Sun Microsystem开发的,旨在提供一个可在任何地方运行的“一次编写”代码的平台。他们创建了一种类似于C ++的面向对象的语言,但是内存安全,因为其运行时错误检测会检查数组范围和指针取消引用。在Internet时代,JVM生态系统变得非常流行,使其成为企业服务器应用程序的实际标准。满足广泛用例的虚拟机以及可满足企业发展需求的令人印象深刻的库集,使广泛的适用性成为可能。
JVM设计时考虑了效率。它的大多数概念是流行操作系统中功能的抽象,例如映射到操作系统线程的线程模型。JVM是高度可定制的,包括垃圾收集器(GC)和类加载器。一些最先进的GC实现提供高度可调整的功能,以适应基于共享内存的编程模型。JVM允许您在程序运行时更改代码。而且,JIT编译器允许将字节码编译为本机代码,目的是加快应用程序的各个部分。
Java世界中的并发性主要与在并行线程中运行应用程序有关,以确保它们是快速的。由于并发原语的共享内存模型带来了挑战,因此使用并发原语进行编程是一项艰巨的任务。为了克服这些困难,人们尝试简化和统一并发编程模型,最成功的尝试是Akka框架

并发与并行
如果部分代码在多个内核,处理器或计算机上同时运行,则我们谈论并行代码执行,而并发编程是指独立处理到达系统的事件。可以在单线程硬件上模拟并发执行,而并行执行则不能。尽管这种区别似乎很古怪,但这种差异导致需要解决的问题非常不同。
想想很多厨师在做一盘Carbonara意大利面。在并行方法中,将任务分配给可用厨师的数量,并且只要完成这些厨师完成其特定任务的速度,就可以完成单个部分。在一个并发的世界中,每位厨师将获得一部分,每位厨师将完成所有任务。您将并行性用于速度,并发性用于规模
并行执行试图将问题的最佳分解解决为彼此独立的部分。将水煮沸,煮意大利面,混合鸡蛋,炸瓜里阿塞火腿,将佩克立诺奶酪磨碎。共享数据(或在我们的示例中为餐盘)由锁,互斥锁和各种其他技术处理,以确保正确性。另一种看待这种情况的方式是数据本身(烹饪原料)是首先存在那里,并且我们希望利用尽可能多的并行CPU资源来尽快完成工作。(分配更多厨师做每个独立部分,尽快完成工作)
另一方面,并​​发编程处理许多事件,这些事件在不同的时间到达系统,并尝试在合理的时间内处理所有事件。在多核或分布式体系结构上,某些执行是并行运行的,但这不是必需的。另一种看待它的方法是,同一位厨师按照始终相同的顺序算法,将水煮沸,煮意大利面,混合鸡蛋等是一个烹饪流程。跨这个烹饪流程的变化是要处理的数据本身(烹饪原料),这些数据(烹饪原料)存在于多个实例(多个厨师烹饪流程)中。
JVM是为并行而构建的,而BEAM是为并发构建的。它们是两个本质上不同的问题,需要不同的解决方案。

BEAM和并发
BEAM提供轻量级流程为正在运行的代码提供上下文。这些进程也称为参与者,不共享内存,而是通过消息传递进行通信,将数据从一个进程复制到另一个进程。消息传递是虚拟机通过各个进程拥有的邮箱实现的功能。消息传递是一种非阻塞操作,这意味着将消息发送到另一个进程几乎是即时的,并且不会阻塞发送者的执行。发送的消息采用不可变数据的形式,从发送过程的堆栈复制到接收者的邮箱。无需在进程之间使用锁和互斥锁即可实现此目的,而在多个进程并行将消息发送到同一收件人的情况下,只需对邮箱进行锁定即可。
不变的数据和消息传递使程序员能够编写彼此独立工作的流程,并专注于功能而不是内存的低级管理和任务调度。事实证明,这种简单的设计不仅适用于单个线程,而且适用于在同一VM中运行的本地计算机上的多个线程,并使用内置的分发,通过具有VM和计算机群集的网络在内部运行。如果消息在进程之间是不可变的,则可以不加锁地将它们发送到另一个线程(或计算机),从而在分布式多核体系结构上几乎线性地扩展。在本地VM上与在VM群集中以相同的方式处理进程,无论接收进程的位置如何,消息发送都是透明的。
进程不共享内存,因此您可以复制数据以恢复弹性并分发数据以实现规模扩展。这意味着在两个不同的机器上具有相同进程的两个实例,彼此之间共享状态更新。如果一台计算机发生故障,则另一台计算机具有数据副本,并且可以继续处理该请求,从而使系统具有容错能力。如果两台计算机都可运行,则两个进程都可以处理请求,从而为您提供可伸缩性。BEAM提供了高度优化的原语,使所有这些无缝地工作,而OTP(“标准库”)提供了更高级别的结构,以简化程序员的生活。
Akka在复制更高级别的结构方面做得很好,但是由于缺少JVM提供的原语而在一定程度上受到了限制,从而使其可以高度优化并发性。尽管JVM的原语支持更广泛的用例,但由于它们没有用于通信的内置原语且通常基于共享内存模型,因此它们使对分布式系统的编程变得更加困难。例如,您在分布式系统中的何处放置共享内存?以及访问它的成本是多少?

调度器
我们提到过,BEAM的最强功能之一就是能够将程序分解为小的,轻量级的过程。管理这些过程是调度程序的任务。与JVM将其线程映射到OS线程并让操作系统调度它们不同,BEAM带有自己的调度程序。
默认情况下,调度程序为每个内核启动一个OS线程,并优化它们之间的工作负载。每个过程都包含要执行的代码和随时间变化的状态。调度程序会选择运行队列中准备运行的第一个进程,并为其执行一定程度的缩减,其中每次缩减都大致等同于命令。一旦进程用尽了减少量,被I / O阻塞,正在等待消息或完成执行其代码,调度程序就会在运行队列中选择下一个进程并进行调度。这种调度技术称为抢先式。
我们多次提到Akka框架,因为它的最大缺点是需要用调度点注释代码,因为调度不是在JVM级别进行的。通过从程序员手中除去控件,可以保留和保证软实时属性,因为不存在它们意外导致进程匮乏的风险。
进程可以散布在可用的调度程序线程周围,并最大程度地利用CPU。有许多方法可以调整调度程序,但是它很少见,仅在边缘和边界情况下才需要,因为默认选项涵盖了大多数使用模式。
关于调度程序,经常出现一个敏感的话题:如何处理本机实现的函数(NIF)。NIF是用C编写的代码片段,被编译为库并在与BEAM相同的内存空间中运行以提高速度。NIF的问题在于它们不是抢占式的,并且会影响调度程序。在最新的BEAM版本中,添加了一项新功能,即脏调度程序,以更好地控制NIF。肮脏的调度程序是在不同线程中运行的单独的调度程序,以最大程度地减少NIF对系统造成的中断。脏这个词是指这些调度程序运行的代码的性质。

垃圾收集器
BEAM的特点是,仅在需要时才在每个进程的基础上运行垃圾回收,而不会影响在运行队列中等待的其他进程。结果,Erlang中的垃圾收集不会“停止世界”。它可以防止处理延迟高峰,因为VM不会从整体上停止-仅特定进程停止,并且绝不会同时停止所有进程。

热代码加载
热代码加载可能是BEAM引用最多的独特功能。热代码加载意味着可以通过更改系统中的可运行代码来更新应用程序逻辑,同时保留内部流程状态。这是通过替换已加载的BEAM文件并指示VM替换正在运行的进程中的代码引用来实现的。
对于电信基础架构无需停机代码升级而言,这是一项至关重要的功能,在该基础架构中,冗余硬件用于处理高峰流量。开发人员可以通过替换部分代码来加快迭代速度,而不必重新启动系统来对其进行测试。即使该应用程序并非设计为可在生产环境中进行升级,也可以减少重新编译和重新部署所需的时间。

何时不使用BEAM
速度从来都不是Beam优先考虑事项,电信基础设施不需要快速运行,数字运算最好留给JVM,Go或其他编译成本地语言的语言使用。毫不奇怪,在JVM上运行的Erlang版本Erjang上的浮点运算比BEAM快5000%。但是我们看到BEAM大放异彩的地方是利用它的并发来安排管理数字运算,将具体脏活外包给C,Julia,Python或Rust。
对于具有更多处理能力的嵌入式系统(多核已成为常态),您需要并发性,而BEAM令人眼前一亮。上世纪90年代,我们实现了电话交换机,以处理运行在具有16mb内存的嵌入式板上的成千上万的用户。
在诸如GRiSP之类的裸机上运行的Erlang VM的实现将为您提供类似的保证。