erlang是否以任何聪明的方式实现了记录复制和修改?

时间:2011-08-26 16:39:32

标签: memory erlang record internals

下式给出:

-record(foo, {a, b, c}).

我这样做:

Thing = #foo{a={1,2}, b={3,4}, c={5,6}},
Thing1 = Thing#foo{a={7,8}}.

从语义上看,Thing和Thing1是唯一的实体。但是,从语言实现的角度来看,制作Thing的完整副本以生成Thing1将是非常浪费的。例如,如果记录的大小为兆字节,并且我制作了一千个“副本”,每个修改了几个字节,那我就烧掉了一个千兆字节。如果内部结构跟踪父结构的表示,并且每个衍生物以指示其自身变化的方式标记该父项,但保留了每个elses的版本,则可以使用最小的内存开销创建派生。

我的问题是:erlang在内部做任何聪明的事情 - 以保持通常的erlang涂鸦的开销;

Thing = #ridiculously_large_record,
Thing1 = make_modified_copy(Thing),
Thing2 = make_modified_copy(Thing1),
Thing3 = make_modified_copy(Thing2),
Thing4 = make_modified_copy(Thing3),
Thing5 = make_modified_copy(Thing4)

......至少?

我问,因为如果是这种情况,我会进行跨进程通信的方式会有很多变化。

4 个答案:

答案 0 :(得分:9)

只有少数人知道垃圾收集和内存分配的确切工作方式。值得庆幸的是,他们非常乐意分享他们的知识,以下是基于我从erlang-questions邮件列表中学到的知识以及与OTP开发人员讨论的内容。

在进程之间进行消息传递时,始终会复制内容,因为进程之间没有共享堆。唯一的例外是大于64字节的二进制文件,其中只复制了一个引用。

在一个进程中执行代码时,只更新部件。让我们分析一下元组,因为这就是你提供的例子。

元组实际上是一个结构,它保持对堆上某处的实际数据的引用(除了小整数以及可能还有一种我不记得的数据类型)。当您使用例如setelement/3更新元组时,会创建一个替换了给定元素的新元组,但是对于所有其他元素,仅复制引用。有one exception这是我从未能够利用的。

垃圾收集器跟踪每个元组并了解何时可以安全地回收任何不再使用的元组。可能是元组引用的数据仍在使用中,在这种情况下,数据本身不会被收集。

与往常一样,Erlang为您提供了一些工具来准确了解正在发生的事情。 The efficiency guide详细说明了如何在进程内部使用和复制时使用erts_debug:size/1erts_debug:flat_size/1来了解数据结构的大小。跟踪工具还允许您了解垃圾收集的时间,内容和数量。

答案 1 :(得分:5)

记录foo是arity四(持有四个单词),但整个结构大小为14个单词。任何立即(pids,端口,小整数,原子,catch和nil)都可以直接存储在元组数组中。任何其他不能容纳某个单词的术语,例如其他元组,都不会直接存储,而是由盒装指针引用(盒装指针是一个带有转发地址的erlang术语,指向实际的eterm ......只是内部)。 / p>

在你的情况下,创建一个相同arity的新元组,原子foo和所有指针都从前一个元组复制,除了索引2 a,它指向新元组{ {1}}构成3个单词。在所有5 + 3个新单词在堆上创建,只有3个单词从旧元组复制,其他9个单词不被触及。

不建议使用过大的元组。更新元组时,需要复制整个元组,即数组而不是深层内容,然后在其他元素中更新以保留持久数据结构。这也会产生增加的垃圾,迫使垃圾收集器升温,这也会损害性能。由于这个原因,{7,8}dict模块避免使用大元组,而是使用浅层元组树。

答案 2 :(得分:2)

我绝对可以验证人们已经指出的内容:

  • 记录只是一个元组,记录名称作为第一个元素,所有字段只是下面的元组元素
  • 当更改元组的元素时,更新案例中记录中的字段,只有顶级元组是新的,所有元素都只是重用

这只是因为我们有不可变数据。因此,在您的示例中,每次更新#foo记录中的值时,都不会复制元素中的任何数据,只会创建新的4元素元组(5个字)。 Erlang永远不会在这种类型的操作中或在函数调用中传递参数时进行深层复制。

答案 3 :(得分:0)

总结:

Thing = #foo{a={1,2}, b={3,4}, c={5,6}},
Thing1 = Thing#foo{a={7,8}}.

在这里,如果不再使用Thing,它可能会在适当的位置更新,并且将避免复制元组,正如效率指南所述。 (我认为元组和记录语法符合setelement的内容)

Thing = #ridiculously_large_record,
Thing1 = make_modified_copy(Thing),
Thing2 = make_modified_copy(Thing1),
...

这里每次都会复制元组。

我想理论上可以对此进行有趣的优化。如果编译器可以对make_modified_copy的返回值执行转义分析并检测到对它的唯一引用是返回的,则可以保存有关该函数的信息。当遇到调用该函数时,它会知道修改返回值是安全的。

由于代码替换功能,这只能在模块间调用上进行。

也许有一天我们会拥有它。