[Erlang 0127] Term sharing in Erlang/OTP 上篇
之前,在 [Erlang 0126] 我们读过的Erlang论文 提到过下面这篇论文:
On Preserving Term Sharing in the Erlang Virtual Machine
地址: http://user.it.uu.se/~kostis/Papers/erlang12_sharing.pdf
摘要:In this paper we describe our experiences and argue through examples why flattening terms during copying is not a good idea for
a language like Erlang. More importantly, we propose a sharing preserving copying mechanism for Erlang/OTP and describe a pub-
licly available complete implementation of this mechanism.
Term Sharing 数据项共享这个东西不新鲜,"Efficiency Guide User's Guide"的4.2章节"Constructing binaries"[ 链接 ] 和 8.2 章节的"Loss of sharing" [链接]就提到过(再次吐槽一下Erlang文档组织,一个主题往往分散在多个文档里面,需要耐心).不仅仅是Binary数据类型,Term Sharing 是一个通用的问题.在Guide中提到了强制拷贝的场景:发送到别的进程或者插入到ETS. 下面是文档摘抄的:
Loss of sharing
Shared sub-terms are not preserved when a term is sent to another process, passed as the initial process arguments in the spawn call, or stored in an ETS table. That is an optimization. Most applications do not send messages with shared sub-terms.
数据拷贝过程,Erlang会进行两次遍历.第一次遍历,会计算flat size(erts/emulator/beam/copy.c中的size_object 方法)然后为此分配对应的内存,第二次遍历完成实际的拷贝 (function copy_structin erts/emulator/beam/copy.c).
首先,我们写一个简单的代码演示一下后面要频繁用到的erts_debug:size/1和erts_debug:flat_size/1
s3(L)->
L2=[L,L,L,L],
{{erts_debug:size(L),erts_debug:flat_size(L)},
{erts_debug:size(L2),erts_debug:flat_size(L2)}}
. 9> d:s3([1,2,3,4,5,6]).
{{12,12},{20,56}}
下面在shell这段代码,分别演示了spawn,消息发送,以及插入ETS.
Eshell V6.0 (abort with ^G)
1> L=[1,2,3,4,5,6,7,8,9,10].
[1,2,3,4,5,6,7,8,9,10]
2> L2=[L,L,L,L,L,L].
[[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10]]
3> erts_debug:size(L2).
32
4> erts_debug:flat_size(L2).
132
5> spawn(fun () ->receive Data -> io:format("~p",[erts_debug:size(Data)]) end end).
<0.39.0>
6> v(5) ! L2.
132[[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10]]
7> erts_debug:size(L2).
32
8> ets:new(test,[named_table]).
test
9> ets:insert(test,{1,L2}).
true
10> ets:lookup(test ,1).
[{1,
[[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10]]}]
11> [{1,Data}]=v(10).
[{1,
[[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10]]}]
12> Data.
[[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10],
[1,2,3,4,5,6,7,8,9,10]]
13> erts_debug:size(Data).
132
14> spawn(d,test,[L2]).
132<0.54.0> test(Data)->
io:format("~p",[erts_debug:size(Data)]).
除了上面的情况,还有一些潜在的情况也会导致数据展开,比如上面提到的论文里面设计的例子:
show_printing_may_be_bad() ->
F = fun (N) ->
T = now(),
L = mklist(N),
S = erts_debug:size(L),
io:format("mklist(~w), size ~w, ", [N, S]),
io:format("is ~P, ", [L, 2]), %%% BAD !!!
D = timer:now_diff(now(), T),
io:format("in ~.3f sec.~n", [D/1000000])
end,
lists:foreach(F, [10, 20, 22, 24, 26, 28, 30]). mklist(0) -> 0;
mklist(M) -> X = mklist(M-1), [X, X].
io:format("is ~P, ", [L, 2]), %%% BAD !!!这行代码删掉前后分别执行代码,在过机器上得到的结果如下:
Eshell V6.0 (abort with ^G)
1> d:show_printing_may_be_bad().
mklist(10), size 40, in 0.001 sec.
mklist(20), size 80, in 0.000 sec.
mklist(22), size 88, in 0.000 sec.
mklist(24), size 96, in 0.000 sec.
mklist(26), size 104, in 0.000 sec.
mklist(28), size 112, in 0.000 sec.
mklist(30), size 120, in 0.000 sec.
ok Eshell V6.0 (abort with ^G)
1> d:show_printing_may_be_bad().
mklist(10), size 40, is [[...]|...], in 0.001 sec.
mklist(20), size 80, is [[...]|...], in 0.110 sec.
mklist(22), size 88, is [[...]|...], in 0.421 sec.
mklist(24), size 96, is [[...]|...], in 43.105 sec.
mklist(26), size 104,
Crash dump was written to: erl_crash.dump
eheap_alloc: Cannot allocate 3280272216 bytes of memory (of type "heap").
rlwrap: warning: erl killed by SIGABRT.
rlwrap has not crashed, but for transparency,
it will now kill itself (without dumping core)with the same signal
很明显看到有这行代码的版本不仅执行时间长,而且需要大量内存.
为什么会出现这种情况呢?就是上面提到的"Loss of sharing",为什么会触发数据展开(或者说数据平铺化)呢?之前我们曾经聊过关于io:format的事情( [Erlang 0041] 详解io:format [链接]),在Erlang/OTP中I/O是通过向I/O Server发起I/O请求实现的.io:format调用实际上是向I/O 发送了一个io request消息,剩下的就由IO Server处理了.虽然上面的L被简略的输出为" [[...]|...]"但是在消息的传递过程中已经触发了数据平铺然后拷贝;
这种问题实际上是非常隐蔽的,所以通过宏选项在release生成的时候清理掉所有io:format输出是非常有必要的;
[Erlang 0127] Term sharing in Erlang/OTP 上篇的更多相关文章
- [Erlang 0128] Term sharing in Erlang/OTP 下篇
继续昨天的话题,昨天提到io:format对数据共享的间接影响,如果是下面两种情况恐怕更容易成为"坑", 呃,恰好我都遇到过; 如果是测试代码是下面这样,得到的结果会是怎样?猜! ...
- [Erlang 0121] 当我们谈论Erlang Maps时,我们谈论什么 Part 3
Erlang/OTP 17.0 has been released http://www.erlang.org/download/otp_src_17.0.readme Erlang/OTP ...
- Erlang基础 -- 介绍 -- 历史及Erlang并发
前言 最近在总结一些Erlang编程语言的基础知识,拟系统的介绍Erlang编程语言,从基础到进阶,然后再做Erlang编程语言有意思的库的分析. 其实,还是希望越来越多的人关注Erlang,使用Er ...
- [Erlang 0117] 当我们谈论Erlang Maps时,我们谈论什么 Part 2
声明:本文讨论的Erlang Maps是基于17.0-rc2,时间2014-3-4.后续Maps可能会出现语法或函数API上的有所调整,特此说明. 前情提要: [Erlang 0116] 当我们谈论E ...
- [Erlang 0116] 当我们谈论Erlang Maps时,我们谈论什么 Part 1
Erlang 增加 Maps数据类型并不是很突然,因为这个提议已经进行了2~3年之久,只不过Joe Armstrong老爷子最近一篇文章Big changes to Erlang掀起不小了风 ...
- 学习:erlang的term反序列化,string转换为term
一. string_to_term(String) -> case erl_scan:string(String++".") of {ok, Tokens ...
- [Erlang 0125] Know a little Erlang opcode
Erlang源代码编译为beam文件,代码要经过一系列的过程(见下面的简图),Core Erlang之前已经简单介绍过了Core Erlang,代码转换为Core Erlang,就容易拨开一些语法糖的 ...
- [Erlang 0109] From Elixir to Erlang Code
Elixir代码最终编译成为erlang代码,这个过程是怎样的?本文通过一个小测试做下探索. 编译一旦完成,你就看到了真相 Elixir代码组织方式一方面和Erlang一样才用非常 ...
- [Erlang 0119] Erlang OTP 源码阅读指引
上周Erlang讨论群里面提到lists的++实现,争论大多基于猜测,其实打开代码看一下就都明了.贴出代码截图后有同学问这代码是哪里找的? "代码去哪里找?",关于Erla ...
随机推荐
- MVVM大比拼小结
目前完成4篇源码分析文章: MVVM大比拼之knockout.js源码精析 MVVM大比拼之avalon.js源码精析 MVVM大比拼之vue.js源码精析 MVVM大比拼之AngularJS源码精析 ...
- What is Away3D
做了几个基于Flash平台的3D的项目,一路走来收获颇多.Away3D作为一个开源的Flash3D引擎,在3D页游领域,无疑是当前OGRE在国内的地位. 翻译出了多年前做Away3D中国社区的时候翻译 ...
- 再次思考 classpath 环境变量 等
f:\aspectj1.8\lib\aspectjrt.jar;.;%JAVA_HOME%\lib;C:\Temp\IBM\SQLLIB\java\db2java.zip;C:\Temp\IBM\SQ ...
- JavaScript随笔3
1.获取非行间css if(oDiv.currentStyle){ alert(oDiv.currentStyle.width); }else{ alert(oDiv.getComputedStyle ...
- python code
执行动态语句 执行字符串中的代码 http://www.cnblogs.com/fanweibin/p/5418817.html #!usr/bin/env python #coding:utf-8 ...
- 将Json数据保存在静态脚本文件中读取
一些常用的数据例如一些网站的区域信息被改变的可能性不大,一般不通过请求获取,于是我们选择存在静态文件中,例如以下Demo: 1.动态加载Json数据显示到前台 [HttpPost] public Ac ...
- Android性能优化之App应用启动分析与优化
前言: 昨晚新版本终于发布了,但是还是记得有测试反馈app启动好长时间也没进入app主页,所以今天准备加个班总结一下App启动那些事! app的启动方式: 1.)冷启动 当启动应用时,后台没 ...
- 原创:去繁存简,回归本源:微信小程序公开课信息分析《一》
以前我开过一些帖子,我们内部也做过一些讨论,我们从张小龙的碎屏图中 ,发现了重要讯息: 1:微信支付将成为重要场景: 2:这些应用与春节关系不小,很多应用在春节时,有重要的场景开启可能性: 3:春节是 ...
- 【知识积累】try-catch-finally+return总结
一.前言 对于找Java相关工作的读者而言,在笔试中肯定免不了遇到try-catch-finally + return的题型,需要面试这清楚返回值,这也是这篇博文产生的由来.本文将从字节码层面来解释为 ...
- c#编程基础之字符串基础
1.C#中单个的字符串用单引号包含就是char类型,('a'),单引号中放且只能放一个字符 2.单个字符也可以表示为字符串,还可以有长度为0的字符串. 3.使用s.Length属性来获得字符串中的字符 ...