从技术上讲,为什么 Erlang 的进程比操作系统线程更高效?

二郎的特点

Erlang 编程(2009)开始:

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

Erlang 面向并发编程(pdf) (幻灯片)(2003) :

我们观察到,创建一个 Erlang 进程所需的时间是常数1μs,最多可达2,500个进程; 此后,最多可达30,000个进程的时间增加到约3μs。Java 和 C # 的性能如图的顶部所示。对于少数进程,大约需要300μs 来创建一个进程。创建超过2000个进程是不可能的。

我们看到,对于多达30,000个进程,在两个 Erlang 进程之间发送消息的时间约为0.8 μs。对于 C # ,每条消息大约需要50μs,最多可以达到最大进程数(大约1800个进程)。Java 更糟糕,对于多达100个进程,每条消息需要50μs,此后,当有大约1000个 Java 进程时,每条消息需要10ms。

我的想法

从技术上讲,我不能完全理解为什么 Erlang 进程在产生新进程时效率更高,而且每个进程占用的内存更少。操作系统和 Erlang VM 都必须执行调度、上下文切换和跟踪寄存器中的值等操作。

为什么操作系统线程的实现方式与 Erlang 的进程不同呢?他们必须支持更多的东西吗?为什么他们需要更大的内存占用?为什么它们的产卵和交流速度较慢?

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

继续读

34413 次浏览

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

Erlang 进程在其他语言中大致对应于 绿线; 进程之间没有操作系统强制的分离。(很可能存在语言强制的分离,但这种保护程度较低,尽管 Erlang 做得比大多数人都好。)因为它们非常轻,所以可以被更广泛地使用。

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


另一种理解区别的方法是这样的。假设你要在 jVM 之上编写一个 Erlang 的实现(这个建议并不是特别疯狂) ,那么你会让每个 Erlang 进程都是一个带有某种状态的对象。然后会有一个运行 Erlang 进程的 Thread 实例池(通常根据主机系统中的核心数量调整大小; 这在实际的 Erlang 运行时 BTW 中是一个可调参数)。反过来,这将把要完成的工作分布到实际可用的系统资源中。这是一种非常简洁的方法,但是它依赖于 绝对的,因为每个 Erlang 进程并没有做很多事情。这当然没问题; Erlang 的结构不要求这些单独的进程是重量级的,因为执行程序的是它们的整体集成。

在许多方面,真正的问题在于术语。Erlang 所调用的进程(与 CSP,CCS,特别是 π- 演算中的相同概念强烈对应)与具有 C 传统的语言(包括 C + + ,Java,C # 和许多其他语言)所调用的进程或线程完全不同。一些有一些相似之处(都涉及到并发执行的一些概念) ,但是肯定没有等价性。所以当有人对你说“过程”的时候要小心,他们可能会理解为完全不同的意思。

有以下几个因素:

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

经过更多的研究,我发现了乔 · 阿姆斯特朗的一个演讲。

Erlang-用于并发世界的软件(表示)开始(13分钟) :

Erlang 是一种并发语言——我的意思是线程是编程语言的一部分,它们不属于操作系统。这正是 Java 和 C + + 等编程语言的问题所在。它的线程不是编程语言,线程是操作系统中的东西——它们继承了操作系统中的所有问题。其中一个问题是内存管理系统的粒度。那实际上太大了。

如果你给你的机器增加更多的内存——你有相同数量的比特来保护内存,所以页表的粒度会增加 -< b > 你最终会使用比如64kB 来处理一个你知道运行在几百个字节里的进程。

我认为它回答了,如果不是全部,至少是我的一些问题

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

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

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

因此,Erlang 进程速度更快,扩展性更好,因为切换的基本操作更快,调度程序运行的次数也更少。

我认为 Jonas 想要一些比较操作系统线程和 Erlang 进程的数据。《 Programming Erlang 》一书的作者乔 · 阿姆斯特朗不久前测试了 Erlang 进程在操作系统线程中的扩展性。他在 Erlang 编写了一个简单的 web 服务器,并在多线程的 Apache 上进行了测试(因为 Apache 使用的是操作系统线程)。有个老网站的数据可以追溯到1998年。我只找到过那个网站一次。所以我不能提供链接。但是消息已经泄露出去了。研究的主要观点表明,Apache 最大处理量不到8K 进程,而他手写的 Erlang 服务器处理10K + 进程。

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