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

Jon*_*nas 165 erlang multithreading lightweight-processes green-threads

Erlang的特点

来自Erlang Programming(2009):

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

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

我们观察到创建Erlang进程所需的时间是1μs到2,500个进程; 此后,对于多达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线程更有效?为什么操作系统中的线程不能以同样有效的方式实现和管理?为什么操作系统线程的内存占用量更大,产生和通信速度更慢?

更多阅读

Mar*_*tos 108

有几个因素:

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

  • 第二点:如果进程尚未运行,则没有理由为其分配堆栈.此外:通过摆弄流程的GC可以播放几个技巧,使其永远不会收集内存.但这是先进的,有点危险:) (6认同)
  • @nilskp :( RE:你评论第3点......)即使语言本身有一个不可变类型系统,底层实现 - 消息传递,调度程序等 - 完全是一个不同的故事.只需轻轻一按开关即可实现正确有效的SMP支持. (6认同)
  • 第3点:Erlang强制执行不可变数据,因此引入SMP不应影响线程安全性. (3认同)

Jon*_*nas 68

经过一些研究后,我找到了Joe Armstrong的演讲.

来自Erlang - 并发世界的软件(演示文稿)(13分钟):

[Erlang]是一种并发语言 - 我的意思是线程是编程语言的一部分,它们不属于操作系统.这就是Java和C++等编程语言的真正问题.它的线程不是编程语言,线程是操作系统中的东西 - 它们继承了操作系统中的所有问题.其中一个问题是内存管理系统的粒度. 操作系统中的内存管理可以保护整个内存页面,因此线程可以使用的最小大小是页面的最小大小. 这实际上太大了.

如果你为你的机器增加了更多的内存 - 你拥有相同数量的位来保护内存,所以页面表的粒度会上升 - 你最终会使用64kB来表示你知道在几百字节内运行的进程.

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

  • 您应该查看Microsoft Research的Singularity Operating System.在Singularity中,所有代码,内核,设备驱动程序,库和用户程序都在具有完全内核特权的ring 0中运行.所有代码,内核,设备驱动程序,库和用户程序都在一个扁平的物理地址空间中运行,无需任何内存保护.该团队发现语言的保证比MMU可以做出的保证强得多,同时使用MMU的性能成本高达30%(!!!).那么,为什么要使用MMU,如果你的语言已经完成了呢? (3认同)
  • **相关:**http://stackoverflow.com/questions/2267545/why-are-threads-called-as-light-weight-processes (2认同)
  • 堆栈上的内存保护是有原因的.Erlang是不是通过处理器的MMU保护不同执行上下文的堆栈?(并且只希望最好的?)如果一个线程使用超过它的小堆栈怎么办?(是否检查了所有堆栈分配以查看是否需要更大的堆栈?堆栈是否可移动?) (2认同)
  • @Thanatos:Erlang 不允许程序访问内存或摆弄堆栈。所有分配都必须通过托管运行时,包括堆和堆栈。换句话说:硬件保护是无用的,因为它可以防止无论如何都不会发生的事情。该语言是指针安全的、堆栈安全的、内存安全的和类型安全的。一个进程不能使用超过它的“小堆栈”,因为堆栈会根据需要增长。你可能会认为它是微小的反面:无限大。(但懒惰分配。) (2认同)

小智 46

我在汇编程序中实现了协同程序,并测量了性能.

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

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

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


Don*_*ows 32

Erlang进程(大约)与其他语言中的绿色线程对应; 这些进程之间没有操作系统强制分离.(可能存在语言强制分离,但尽管Erlang比大多数人做得更好,但这种保护较少.)因为它们的重量轻得多,所以可以更广泛地使用它们.

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


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

在许多方面,真正的问题是术语之一.Erlang称之为进程的东西(与CSP,CCS中的相同概念强烈对应,尤其是π演算)与具有C遗产的语言(包括C++,Java,C#和许多其他人)调用进程或线程.有一些相似之处(都涉及一些并发执行的概念),但肯定没有等价.所以当有人对你说"过程"时要小心; 他们可能会理解它意味着完全不同的东西......

  • Erlang没有接近Pi Calculus.Pi演算假定可以绑定到变量的通道上的同步事件.这种概念根本不适合Erlang模型.尝试加入微积分,Erlang更接近于它,虽然它仍然需要能够原生地加入一些消息和诸如此类的东西.有一篇名为JErlang的论文(和项目)专门实现了它. (3认同)