[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 ...
随机推荐
- 通过扩展让ASP.NET Web API支持JSONP
同源策略(Same Origin Policy)的存在导致了"源"自A的脚本只能操作"同源"页面的DOM,"跨源"操作来源于B的页面将会被拒 ...
- 游戏编程系列[1]--游戏编程中RPC协议的使用
RPC(Remote Procedure Call Protocol)--远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些传输协议的存在 ...
- RangePartitioner 实现简记
摘要: 1.背景 2.rangeBounds 上边界数组源码走读 3.RangePartitioner的sketch 源码走读 4.determineBounds 源码走读 5.关于RangePart ...
- js自动提示查询添加功能(不是自动补全)
在工作中遇到查询某些数据,并添加到一个列表里的时候,写了一个小功能. 优点: 1.纯手工JS代码,不需要任何js框架,复制下来就能测试,无毒副作用. 2.通过模糊查询快速定位数据,并添加到列表里. 缺 ...
- iOS---正确使用NSNotification对象
我们在开发程序的时候,程序内不同对象间的通信是不可避免的,iOS中主要有以下这些通信方式: iOS中的通信方式 图中按照耦合度的强弱和通信的形式(一对一还是一对多)进行了划分,这篇文章我们主要说一下N ...
- java单例设计模式
单例模式的特点: 1.单例类只能有一个对象(实例). 2.单例类必须自己创建自己的唯一实例 . 3.单例类必须给所有其他对象提供这一实例. 设置步骤: 1.将对象的应用成员变量用private来修饰. ...
- 解析大型.NET ERP系统 数据审计功能
数据审计,英语表达是Audit,是追踪数据变化的过程,记录数据变化前后的值,供参考分析.通过设置,ERP可以追踪一个表的所有字段的变化,也可以只记录指定的字段的值变化.欧美企业每年都有独立的审计部门, ...
- PHP实现全排列(递归算法)
算法描述:如果用P表示n个元素的全排列,而Pi表示n个元素中不包含元素i的全排列,(i)Pi表示在排列Pi前面加上前缀i的排列,那么n个元素的全排列可递归定义为: ① 如果n=1,则排列P只有一 ...
- 提升用户体验的最佳免费 jQuery 表单插件
网页表单是一个老生常谈的话题.出于这样或那样的目的,一些示例中都会包括用户注册,电子商务结算,用户设置甚至联系人表格.而输入栏是非常容易用现代的CSS3技术来应用样式.但是到底什么决定整体用户体验? ...
- ASP.NET Core官方计划路线及需要废除的一些Framework技术
概述 下面是 ASP.NET Core的时间表和路线图. 注意日期和特性都可能更改. 作为.NET Core这么大的一个项目,很难准确预测每一个计划的是否有变动. 即便如此,我们还是计划公开和透明的实 ...