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

Technically, why are processes in Erlang more efficient than OS threads?

二郎的特点

来自Erlang Programming(2009年):

Erlang concurrency is fast and scalable. Its processes are lightweight in that the Erlang virtual machine does not create an OS thread for every created process. They are created, scheduled, and handled in the VM, independent of underlying operating system. As a result, process creation time is of the order of microseconds and independent of the number of concurrently existing processes. Compare this with Java and C#, where for every process an underlying OS thread is created: you will get some very competitive comparisons, with Erlang greatly outperforming both languages.

来自Erlang中面向并发的编程(PDF)(幻灯片)(2003年):

We observe that the time taken to create an Erlang process is constant 1μs up to 2,500 processes; thereafter it increases to about 3μs for up to 30,000 processes. The performance of Java and C# is shown at the top of the figure. For a small number of processes it takes about 300μs to create a process. Creating more than two thousand processes is impossible.

We see that for up to 30,000 processes the time to send a message between two Erlang processes is about 0.8μs. For C# it takes about 50μs per message, up to the maximum number of processes (which was about 1800 processes). Java was even worse, for up to 100 process it took about 50μs per message thereafter it increased rapidly to 10ms per message when there were about 1000 Java processes.

号我的想法

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

简单地说,为什么OS线程的实现方式与Erlang中的进程不同?他们需要支持更多的东西吗?为什么他们需要更大的内存占用?为什么它们的繁殖和交流速度较慢?

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

更多阅读

  • 专注于SMP的Erlang VM内部(2008年)
  • Java与Erlang(PDF)中的并发(2004)
  • Java中线程的性能测量和Erlang(1998)中的进程


有几个因素:

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

  • 经过更多的研究,我找到了乔·阿姆斯特朗的演讲。

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

    [Erlang] is a concurrent language – by that I mean that threads are part of the programming language, they do not belong to the operating system. That's really what's wrong with programming languages like Java and C++. It's threads aren't in the programming language, threads are something in the operating system – and they inherit all the problems that they have in the operating system. One of the problems is granularity of the memory management system. The memory management in the operating system protects whole pages of memory, so the smallest size that a thread can be is the smallest size of a page. That's actually too big.

    If you add more memory to your machine – you have the same number of bits that protects the memory so the granularity of the page tables goes up – you end up using say 64kB for a process you know running in a few hundred bytes.

    我想它至少能回答我的几个问题


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

    在一个现代处理器上,在协程(也称为Erlang进程)之间切换需要大约16条指令和20纳秒。此外,您通常知道要切换到的进程(例如:在队列中接收消息的进程可以实现从调用进程直接切换到接收进程),因此调度程序不会发挥作用,使其成为O(1)操作。

    要切换操作系统线程,大约需要500-1000纳秒,因为您要调用内核。操作系统线程调度程序可能在O(日志(n))或O(日志(n))时间内运行,如果您有数万个甚至数百万个线程,这将开始变得明显。

    因此,Erlang进程更快,扩展性更好,因为交换的基本操作更快,调度程序运行的频率更低。


    Erlang进程(大约)与其他语言中的绿色线程对应;进程之间没有操作系统强制的分离。(很可能有语言强制分离,但这是一个较小的保护,尽管二郎比大多数人做得更好。)因为他们是如此轻的重量,他们可以更广泛地使用。

    另一方面,操作系统线程可以简单地安排在不同的CPU核心上,并且(大部分)能够支持独立的CPU绑定处理。操作系统进程类似于操作系统线程,但具有更强的操作系统强制分离功能。这些功能的代价是操作系统线程和(甚至更高)进程更昂贵。

    另一种理解差异的方法是这样。假设您要在JVM之上编写Erlang的实现(不是特别疯狂的建议),那么您将使每个Erlang进程都成为具有某种状态的对象。然后,您将拥有一个线程实例池(通常根据主机系统中核心的数量来调整大小;这是实际的erlang runtimes btw中的一个可调参数),用于运行erlang进程。反过来,这将把要完成的工作分配到可用的实际系统资源上。这是一种非常整洁的方式,但完全依赖于这样一个事实,即每个Erlang进程并没有做太多的事情。当然没关系;Erlang的结构不要求这些单独的进程是重量级的,因为执行程序的是它们的整体集合。

    在许多方面,真正的问题是术语之一。Erlang调用进程(并且对应于CSP、CCS中的同一概念,特别是π演算)的东西与C继承的语言(包括C++、爪哇、C等许多)调用进程或线程的情况完全不同。有一些相似之处(都涉及到并发执行的概念),但绝对没有等价性。所以当有人对你说"过程"时要小心;他们可能会理解这意味着完全不同的东西…


    我认为乔纳斯想要一些比较OS线程和Erlang进程的数字。编写Erlang的作者JoeArmstrong不久前测试了Erlang进程生成到OS线程的可伸缩性。他在Erlang中编写了一个简单的Web服务器,并对它进行了多线程Apache测试(因为Apache使用OS线程)。有一个旧网站的数据可以追溯到1998年。我只找到了那个网站一次。所以我不能提供链接。但信息就在外面。研究的要点表明,Apache的最大处理能力不到8K个进程,而他手写的Erlang服务器处理的是10多个进程。


    因为Erlang解释器只需要担心自己,所以操作系统还有很多其他的事情需要担心。


    其中一个原因是erlang进程不是在操作系统中创建的,而是在evm(erlang虚拟机)中创建的,因此成本较低。