从技术上讲,为什么Erlang中的进程比OS线程更有效率?

Erlang的特点

来自Erlang编程 (2009):

Erlang的并发性是快速和可扩展的。 其进程是轻量级的,因为Erlang虚拟机不会为每个创build的进程创build一个OS线程。 它们是在虚拟机中创build,调度和处理的,与底层操作系统无关。 因此,进程创build时间是微秒级的,并且与现有进程的数量无关。 将它与Java和C#进行比较,在每个进程中创build一个底层操作系统线程:您将得到一些非常有竞争力的比较结果,Erlang的性能大大超过了这两种语言。

来自Erlang的并发导向编程(pdf) (幻灯片) (2003):

我们观察到创build一个Erlang过程所花费的时间是恒定的1μs到2500个过程; 此后,对于多达30000个过程,其增加到约3μs。 Java和C#的性能显示在图的顶部。 对于less量的stream程来说,创build一个stream程大约需要300μs。 创build两千多个进程是不可能的。

我们看到,对于多达30,000个进程,在两个Erlang进程之间发送消息的时间大约是0.8μs。 对于C#,每个消息大约需要50μs,直到最大进程数(大约1800个进程)。 Java甚至更糟糕,对于高达100个进程,每个消息花费大约50μs,而当有大约1000个Java进程时,每个消息快速增加到10ms。

我的想法

我没有完全理解为什么Erlang进程在产生新进程方面更有效率,并且每个进程的内存占用更小。 操作系统和Erlang虚拟机都必须执行调度,上下文切换以及跟踪寄存器中的值等。

简单地说,为什么OS线程不像Erlang中的进程一样实现呢? 他们需要更多的支持吗? 为什么他们需要更大的内存空间? 为什么他们的产卵和交stream比较慢?

从技术上讲,为什么Erlang中的进程比OS线程在产卵和通信方面更有效率? 为什么操作系统中的线程不能以同样有效的方式实现和pipe理? 为什么操作系统线程有更大的内存占用,再加上较慢的产卵和通信?

更多的阅读

  • Erlang VM内部重点关注SMP (2008)
  • Java和Erlang中的并发(pdf) (2004)
  • Java和进程中的线程性能测量(Erlang ,1998)

有几个促成因素:

  1. Erlang进程不是OS进程。 它们由Erlang虚拟机使用一个轻量级的协作线程模型来实现(在Erlang级抢占,但是在合作调度的运行时的控制下)。 这意味着切换上下文要便宜得多,因为它们只切换到已知的控制点,因此不必保存整个CPU状态(正常,SSE和FPU寄存器,地址空间映射等)。
  2. Erlang进程使用dynamic分配的堆栈,启动非常小,并根据需要增长。 这允许产生数千甚至数百万个Erlang进程而不会吸收所有可用的RAM。
  3. Erlang以前是单线程的,这意味着不需要确保进程之间的线程安全。 它现在支持SMP,但同一个调度程序/内核中Erlang进程之间的交互仍然非常轻量级(每个内核有独立的运行队列)。

经过一些更多的研究,我find了乔·阿姆斯特朗的介绍。

来自Erlang – 并发世界软件(演示) (13分钟):

[Erlang]是一种并发语言,我的意思是说,线程是编程语言的一部分,它们不属于操作系统。 这对Java和C ++等编程语言来说确实是错误的。 线程不在编程语言中,线程是操作系统中的东西 – 并且它们inheritance了它们在操作系统中的所有问题。 其中一个问题是内存pipe理系统的粒度。 操作系统中的内存pipe理可以保护整个内存页面,因此线程所能达到的最小尺寸就是页面的最小尺寸。 这实际上太大了。

如果给计算机添加更多的内存,则可以使用相同数量的位来保护内存,这样页表的粒度就会增加最终你会用64kB来处理一个运行在几百字节的进程。

我认为,如果不是全部的话,至less我的一些问题是可以回答的

我已经在汇编程序中实现了协程,并测量了性能。

在协程之间切换,也就是Erlang进程,在现代处理器上需要大约16条指令和20纳秒。 另外,你经常知道你正在切换的进程(例如:在队列中接收消息的进程可以实现为从调用进程到接收进程的直接切换),所以调度器不会发挥作用这是一个O(1)操作。

要切换操作系统线程,大约需要500-1000纳秒,因为您正在调用内核。 操作系统线程调度器可能运行在O(log(n))或O(log(log(n)))时间,如果你有成千上万甚至上百万的线程,那么这个时间就会开始显着。

因此,Erlang进程更快,规模更好,因为切换的基本操作速度更快,调度器运行频率更低。

Erlang进程对应(近似)其他语言的绿色线程 ; 进程之间没有OS强制分离。 (虽然Erlang比大多数人做得更好,但是这可能是语言强制的分离,但是这样做的保护程度较低。)因为它们的重量非常轻,所以可以使用得更广泛。

另一方面,OS线程可以简单地安排在不同的CPU核心上,并且(大部分)能够支持独立的CPU绑定处理。 操作系统进程就像操作系统线程一样,但操作系统强大的分离性更强。 这些function的价格是操作系统线程和(甚至更多)进程更昂贵。


理解差异的另一种方法是这样的。 假设你要在JVM之上写一个Erlang的实现(不是特别疯狂的build议),那么你将使每个Erlang进程成为一个具有某种状态的对象。 然后,您将拥有一个Thread实例池(通常根据主机系统中的内核数量进行调整;这是一个在真正的Erlang运行时间中的可调参数),它们运行Erlang进程。 反过来,这将分配将在可用的实际系统资源上完成的工作。 这是一个相当干净的做事方式,但完全依赖于这个事实,即每个Erlang过程都没有太多的事实。 这当然没问题; Erlang被devise成不要求那些单独的进程是重量级的,因为它是执行程序的整体合奏。

在许多方面,真正的问题是术语之一。 Erlang调用的过程(与CSP,CCS中的相同概念特别是π演算强烈对应)与C遗留的语言(包括C ++,Java,C#和许多其他)调用一个进程或线程。 有一些相似之处(都涉及到一些并发执行的概念),但是绝对没有等价的。 所以当有人说“处理”给你的时候要小心, 他们可能明白这意味着完全不同的东西

由于Erlang解释器只是担心自己,操作系统还有许多其他的事情需要担心。

我想Jonas想要把操作系统线程与Erlang进程进行比较。 编程Erlang的作者Joe Armstrong,后来testing了Erlang进程产生到OS线程的可伸缩性。 他在Erlang编写了一个简单的Web服务器,并对multithreading的Apache进行了testing(因为Apache使用的是OS线程)。 有一个可以追溯到1998年的旧网站。我只pipe理了一次只能find那个网站。 所以我不能提供一个链接。 但是信息就在那里。 研究的主要观点表明,Apache最多只有不到8K的进程,而他手写的Erlang服务器则处理了10K +进程。

原因之一是erlang进程不是在OS中创build的,而是在evm(erlang虚拟机)中创build的,所以成本比较小。

Interesting Posts