Elixir 进程和没有共享堆内存

Posted

技术标签:

【中文标题】Elixir 进程和没有共享堆内存【英文标题】:Elixir processes and no shared heap memory 【发布时间】:2018-03-26 15:12:07 【问题描述】:

Elixir 进程有自己的堆。 如果一个进程想与另一个进程共享一个数据结构,那怎么可能呢? 我想到的一个答案是进程向包含数据结构的另一个进程发送消息。 这是否意味着整个数据结构从一个堆复制到另一个?如果这是真的,是不是效率低下?

【问题讨论】:

【参考方案1】:

TL;DR:

是的,它效率低下。但您在实践中几乎不会注意到这一点。欢迎来到非常安全的编程世界。您可能会使用基于 Erlang 的语言的大部分内容都与网络相关,而网络到目前为止是更大的限制(有时是磁盘或端口 IO)。

另外,另一种选择是可怕的噩梦。无论如何,如果您进行大规模并发编程。

讨论

在考虑“效率”时,需要考虑两种截然不同的背景:

机器在时间、空间和锁定资源方面执行任务是否有效?是否有不引入泄漏抽象的明显捷径? 人类编写、理解和维护效率高吗?

当您考虑效率的这两个方面时,您最终必须将问题归结为时间和金钱——因为就有效地使用工具而言,这才是真正重要的地方。 p>

人类背景

这个效率论点与“Python 比汇编程序效率低得多”的论点非常相似。我曾经争论过同样的事情——直到我负责了几项大型开发工作。我仍然认为 javascript、XML 和其他一些明显糟糕的语言和数据表示是魔鬼,但在一般情况下(定义为“你没有精确的知识和控制您的中断时间与总线读/写和 CPU 周期有关”)语言提供的基本抽象越大(以及该语言越小)越好。

Erlang 在现代、大规模并发系统的环境中胜出,在简单性和语法限制方面甚至击败了大多数其他 EVM 语言(LFE 除外——Richard 说对了, imo)。

以 Elixir 的 syntactic complexity 为例。无论如何,它都不是一种语言(恰恰相反)。但是,就熟悉度而言,对于许多新来者来说更容易,但实际上却要复杂好几倍,而且比任何初始学习曲线。 "Easy" is not at all the same thing as "simple"; “轻松”是熟悉度的问题,而不是实用价值。

机器上下文

范式在执行中是否高效几乎完全取决于底层实现中引用传递(“按指针”)与消息传递(“按值”)的上下文。

正在传递的东西有多大?是否采用了不破坏按值传递消息的抽象的混合方法?

在 Erlang(以及 Elixir 和 LFE 的扩展)中,大多数进程之间传递的消息非常小。真的,真的很小,事实上。大的、不可变的消息几乎总是 Erlang 二进制文件——这些实际上通过引用传递的(稍后会详细介绍)。

大消息比较少见,但考虑到复制的实现方式,即使这也不是什么大问题。为了允许进程自行崩溃并且允许每个进程拥有its own garbage collection schedule(与不可预测的“停止世界”垃圾收集的噩梦场景相反)每个 Erlang 进程都有自己的堆。

这是从两个方面进行的整体优化:

这允许每个进程崩溃而不影响任何东西。 它还允许以某种方式编写每个进程,以便每个赋值通常是一个不可变的标签声明,而不是一个可变的赋值(而不是一个非常危险和/或非常复杂的管理和 schedule 共享数据对象声明)。

所有这些都是启用每个进程的隔离垃圾收集的原因,而这个单一的区别让 Erlang 感觉就像它具有增量垃圾收集,同时实际上在下面实现了一个无聊的普通 GC 模型(只是将它拆分为每个进程)过程)。

但在某些地方,我们确实希望以牺牲底层复杂性为代价(并根据程序员的认知开销方面的难度)进行一些传递引用。

“大”二进制文件是典型的例子。默认情况下,任何大于 64 字节的二进制文件都是共享对象,通过引用(指针)传递而不是通过值传递(复制)。当然,它们仍然是不可变的,这是安全的唯一原因。问题是,如果不使用binary:copy/1,2,任何对较大二进制文件的子部分的引用都会成为对整个二进制文件的引用,所以你会得到一个令人惊讶的结果全局堆中占用的内存量,因为对内存中较大的整体二进制对象的微小片段的二进制引用。这是有问题的(在某些情况下,您必须仔细绘制引用的内容以防止内存泄漏),但这是在 safe 并发。

结论(一些无法量化的基于轶事的指导......)

就我个人而言,我从来没有真正让按值复制成为瓶颈。不止一次。而且我已经编写了 很多 的 Erlang 程序。

您真正的瓶颈几乎总是对外部资源的共享访问,例如磁盘/存储/网络(从概念上讲,它们是同一件事)。无论如何,支付额外的内核或额外的虚拟机/实例比支付程序员追踪应该使用binary:copy/1,2 的情况以及内存速度要便宜得多。并且 CPU 时间只会变得更快和更便宜,因此与让您的昂贵程序员跟踪的实际成本相比,您今天认为的“性能下降”到明年都将成为微不足道的抱怨将来在您的代码中减少愚蠢的速度黑客。

(如果你的程序员并不比你的计算资源贵得多你为什么要雇佣这么糟糕的程序员?!?!?ZOMG!

关于未来的说明...

未来只会越来越多核,并且在大多数情况下会更加并行更加并发。既然 AMD 正在执行其将 1000 多个核心系统带入桌面的愿景,我预测下一场大争夺将是总线速度、通道、缓存管理和核心内存大小的大幅提升。只有这样,所有这些核心才能看到就业机会。

唯一能够利用的语言将是像 Erlang 这样的语言,它们将按值传递消息作为主要方法,并得到混合案例的支持,例如大型二进制引用传递和全局堆对象的显式复制.在这种世界中,卫生范式将变得更加重要,而语言简单将成为使我们免于大量并行性和并发性所带来的复杂性爆炸的要素。

考虑一下“微服务架构”甚至 Docker 的驱动力——人们在不知不觉中遇到问题,然后解决了许多 Erlang 最初旨在解决的相同问题,只是以一种特别的方式。

在大规模多核、大规模并发环境中,按值传递和每个进程拥有一个堆似乎是一种整体优化,考虑到与核心、心轴、存储和存储相比,优秀程序员的成本要高得多。记忆。 (顺便说一句,我认为未来将由更少的程序员编写更持久的并发语言软件,而猴子大军的方法将继续产生本质上短暂的代码库。)

【讨论】:

以上是关于Elixir 进程和没有共享堆内存的主要内容,如果未能解决你的问题,请参考以下文章

面试 -- Java内存布局图以及java各种存储区详解

JVM之堆空间

iOS进程内存分配(页、栈、堆)

父子进程地址空间堆空间

c++ boost进程间交换(复制)非共享和共享字符串向量

JVM篇1认识JVM,内存区域划分,类加载机制