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


170

Erlang的特征

来自Erlang编程(2009):

Erlang并发是快速且可扩展的。它的进程是轻量级的,因为Erlang虚拟机不会为每个创建的进程创建一个OS线程。它们是在VM中创建,计划和处理的,与基础操作系统无关。结果,进程创建时间约为微秒,并且与同时存在的进程数无关。将此与Java和C#进行比较,在Java和C#中为每个进程创建一个底层OS线程:您将获得一些非常有竞争力的比较,其中Erlang的性能大大优于两种语言。

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

我们观察到,创建一个Erlang进程所花费的时间在2,500个进程中恒定为1µs。此后,对于多达30,000个处理,它增加到大约3µs​​。图的顶部显示了Java和C#的性能。对于少量的过程,创建一个过程大约需要300µs。创建两千多个流程是不可能的。

我们看到,对于多达30,000个进程,在两个Erlang进程之间发送消息的时间约为0.8µs。对于C#,每条消息大约需要50µs的时间,最多不超过最大进程数(大约1800个进程)。Java甚至更糟,对于多达100个进程,每条消息花费大约50µs的时间,此后,当大约有1000个Java进程时,它迅速增加到每条消息的10ms。

我的想法

我从技术上不完全理解为什么Erlang进程在生成新进程时效率如此之高,而每个进程的内存占用却少得多。OS和Erlang VM都必须进行调度,上下文切换,并跟踪寄存器中的值等等。

就是为什么OS线程的实现方式与Erlang中的进程不同?他们还需要更多支持吗?为什么他们需要更大的内存空间?为什么它们的生成和通讯速度较慢?

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

更多阅读


1
在试图理解假设为真的原因之前,您需要确定假设是否为真-例如,证据的支持。你有任何引用像对等的比较,证明了一个Erlang过程实际上比(比如说)上了最新的JVM Java线程更有效率?还是直接使用OS进程和线程支持的C应用程序?(在我看来,后者似乎非常非常不可能。前者只是某种程度上的可能性。)我的意思是,在足够有限的环境下(弗朗西斯科的观点),这也许是正确的,但是我想看看这些数字。
TJ Crowder

1
@Donal:与其他许多绝对声明一样。:-)
TJ Crowder 2010年

1
@Jonas:谢谢,但我知道日期(1998-11-02)和JVM版本(1.1.6)并停止了。在过去的11.5 中,Sun的JVM有了相当大的提高(大概是Erlang的解释器也有所提高),特别是在线程领域。(请清楚地说,我并不是说这个假设不正确(并且弗朗西斯科和多纳尔指出了为什么厄兰德可以在那做某事);我是说不应以其面值来考虑。无需检查。)
TJ Crowder 2010年

1
@Jonas:“ ...但是我想你可以在Erlang中做到...”这是“猜测”部分,伙计。:-)您猜测 Erlang的过程切换会扩大到成千上万。您猜测它的性能要比Java或OS线程好。猜测和软件开发人员并不是一个很好的组合。:-)但是我想我已经说了。
TJ Crowder 2010年

17
@TJ Crowder:安装erlang并运行,erl +P 1000100 +hms 100然后键入,{_, PIDs} = timer:tc(lists,map,[fun(_)->spawn(fun()->receive stop -> ok end end) end, lists:seq(1,1000000)]).然后等待大约三分钟以得到结果。就是这么简单 我的笔记本电脑需要每个进程140us和1GB整个RAM。但是它是直接形成外壳的,因此从编译代码中应该更好。
海尼克

Answers:


113

有几个促成因素:

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

6
第二点:如果该进程尚未运行,则没有理由为其分配堆栈。另外:摆弄一个进程的GC可以使它从不收集内存,这可以起到一些技巧。但这是先进的,而且有些危险:)
我给了答案

3
第三点:Erlang强制执行不可变数据,因此引入SMP不会影响线程安全。
nilskp 2012年

@nilskp,是的,erlang也是一种功能编程语言。因此没有“可变”数据。这导致线程安全。
liuyang13年

6
@nilskp:(RE:您对第3点进行评论……)即使语言本身具有不可变的类型系统,其底层实现(消息传递,调度程序等)也完全不同。轻而易举的事就不仅实现了正确而有效的SMP支持。
Marcelo Cantos 2013年

@rvirding:感谢您澄清附录。我已自由地将您的观点整合到我的回答中。
Marcelo Cantos 2014年

73

经过更多研究后,我找到了Joe Armstrong的演示文稿。

Erlang-并发世界(演示)软件(13分钟):

[Erlang]是一种并发语言–意思是说线程是编程语言的一部分,它们不属于操作系统。这确实是Java和C ++等编程语言的错误所在。它不是编程语言中的线程,而是操作系统中的线程-它们继承了操作系统中存在的所有问题。问题之一是内存管理系统的粒度。 操作系统中的内存管理保护整个内存页面,因此线程可以使用的最小大小是页面的最小大小。 那实际上太大了。

如果您在计算机上增加了更多的内存(保护内存的位数相同,因此页表的粒度会增加)那么最终您会说64kB的进程占用数百个字节。

我认为它至少回答了我的几个问题



2
堆栈上的内存保护是有原因的。Erlang是否仅通过处理器的MMU不保护不同执行上下文的堆栈?(只是希望是最好的?)如果线程使用的空间比其很小的堆栈还要多呢?(是否检查所有堆栈分配以查看是否需要更大的堆栈?堆栈是否可移动?)
Thanatos

2
@Thanatos:Erlang不允许程序访问内存或破坏堆栈。所有分配都必须通过托管运行时,包括堆和栈。换句话说:硬件保护是无用的,因为它可以防止无法发生的事情。该语言是指针安全,堆栈安全,内存安全和类型安全的。进程只能使用其“微小堆栈”,因为堆栈会根据需要增长。您可能会认为它与tiny相反:无限大。(但分配得
很懒

4
您应该看看Microsoft Research的Singularity操作系统。在奇点中,所有代码,内核,设备驱动程序,库和用户程序都在具有完全内核特权的环0中运行。所有代码,内核,设备驱动程序,库和用户程序都在单个平面物理地址空间中运行,而没有任何内存保护功能。团队发现,语言所做出的保证比MMU所能做出的保证要强大得多,同时使用MMU会使性能损失高达30%(!!!)。那么,如果您的语言已经使用MMU,为什么还要使用它呢?
约尔格W¯¯米塔格

1
OS / 400操作系统的工作方式相同。所有程序只有一个固定地址空间。当今大多数实际使用的语言都具有相同的安全性(ECMAScript,Java,C♯,VB.NET,PHP,Perl,Python,Ruby,Clojure,Scala,Kotlin,Groovy,Ceylon,F♯,OCaml, “ Objective-C”的“ Objective”部分,“ C ++”的“ ++”部分)。如果不是传统的C代码以及C ++和Objective-C的传统功能,我们甚至都不需要虚拟内存。
约尔格W¯¯米塔格

47

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

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

要切换OS线程,大约需要500-1000纳秒,因为您要调用内核。OS线程调度程序可能在O(log(n))或O(log(log(n(n))))时间中运行,如果您有成千上万甚至数百万个线程,这将开始引起注意。

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


33

Erlang进程(大约)对应于绿色线程于其他语言的;进程之间没有操作系统强制的隔离。(可能存在语言强制的分隔,但这尽管Erlang比大多数人做得更好,但是保护程度较小。)由于它们的重量轻得多,因此可以广泛使用。

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


了解差异的另一种方法是这样。假设您要在JVM之上编写Erlang的实现(这不是一个特别疯狂的建议),那么您将使每个Erlang进程成为具有某种状态的对象。然后,您将拥有一个运行Erlang进程的Thread实例池(通常根据主机系统中的内核数量确定大小;这是真实的Erlang运行时BTW中的可调参数)。反过来,这将在可用的实际系统资源上分配要完成的工作。这是一种非常整齐的处理方式,但是完全依赖因为每个单独的Erlang进程做得并不多。当然可以;Erlang的结构使其不需要繁重的流程,因为执行该程序的是它们的整体集合。

在许多方面,真正的问题是术语之一。Erlang称为进程的事物(与CSP,CCS尤其是π演算中的相同概念高度对应)与具有C继承的语言(包括C ++,Java,C#和许多其他)调用进程或线程。有一些相似之处(都涉及并发执行的概念),但是绝对没有对等的地方。因此,当有人对您说“处理”时要小心;他们可能会理解它的含义完全不同...


3
Erlang离Pi Calculus还远。Pi演算假定可以绑定变量的通道上发生同步事件。这种概念根本不适合Erlang模型。尝试加入微积分(Join Calculus),尽管它仍然需要能够自然地加入一些消息,但仍然需要Erlang。有一篇名为JErlang的论文(和项目)专门实施了该论文。
我给大家带来了糟糕的建议

这完全取决于您对pi演算的实际了解(并且可以使用同步通道和缓冲进程对异步通道进行建模)。
Donal Fellows 2010年

您只是在说Erlang进程是轻量级的,但没有解释为什么它们具有较小的占用空间(轻量级),以及为什么它们具有比OS线程更好的性能。
乔纳斯(Jonas)2010年

1
@Jonas:对于某些类型的任务(尤其是计算繁重的任务),OS线程的性能更好。请注意,这些通常不是使用Erlang的任务;Erlang专注于执行大量简单的通信任务。这样做的好处之一是,在执行一组任务并等待结果的一组任务的情况下,所有这些任务都可以在单个处理器上的单个OS线程中完成,比具有上下文切换。
Donal Fellows 2010年

从理论上讲,您可以通过使用很小的堆栈并仔细控制分配的其他线程特定资源的数量来使OS线程也变得非常便宜,但这在实践中是很成问题的。(预测堆栈需求有些不明智。)因此,特别设计OS线程是为了在线程数量较少(CPU核心数量的数量级)并且它们发挥更大作用的情况下是最佳的每个处理量。
Donal Fellows,2010年

3

我认为Jonas需要一些数字来比较OS线程和Erlang进程。编程Erlang的作者Joe Armstrong不久前测试了将Erlang进程产生到OS线程的可伸缩性。他用Erlang编写了一个简单的Web服务器,并针对多线程Apache进行了测试(因为Apache使用OS线程)。有一个旧的网站,其数据可以追溯到1998年。我只找到了该网站一次。因此我无法提供链接。但是信息在那里。研究的重点表明,Apache最多可以使用8K进程,而他的手写Erlang服务器可以处理10K +进程。


5
我认为您在谈论这一点:sics.se/~joe/apachevsyaws.html但是我问erlang如何使线程比kerlenl线程高效。
乔纳斯(Jonas)2010年

@Jonas链接已死。最后一张快照在这里
alvaro g

1
文章说:“ Apache在大约4,000个并行会话中死亡。Yaws仍在超过80,000个并行连接中起作用。”
内森·朗

请参阅citeseerx.ist.psu.edu/viewdoc/上的全文。确实,事实证明,使用16台攻击机来破坏Erlang服务器是不可能的-尽管很容易停止Apache服务器。
Bernhard


0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.