浏览 9785 次
锁定老帖子 主题:Erlang开发建议(杂记版)
该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-04-24
最后修改:2009-05-09
:) * 确保没有任何编译警告 * Erlang中String采用list实现,32位系统中,其1个字符用8个字节的空间(4个保存value, 4个保存指针)。因此string速度较慢,空间占用较大 * 在Server中,总是尽力书写尾递归(tail-recursive)的函数 * 使用'++'时,left list会被拷贝,然后添加到right list的头部,因此最好把length较短的list放在左侧 * 避免使用regexp,如果需要正则表达式,请使用re * timer模块的大部分函数实现,依赖于一个process,如果过多使用timer,会导致这个process负载过大,影响效率。 推荐使用erlang:send_after/3及erlang:start_timer/3 * 避免使用list_to_atom/1,因为erlang中atom数量最大为1048576, 且不进行GC控制。因此如果持续性的调用list_to_atom/1 可能很容易达到系统上限,从而导致emulator terminate。请使用list_to_existing_atom/1。 * list内部实现为一个列表,因此length(List), 需要遍历整个list比较耗时 * 对于不同的数据类型,使用不同的size函数:tuple_size/1, byte_size/1, bit_size/1 * 使用binary match来进行binary的分割,而不使用split_binary/2 * 如果两个list都拥有很多数据,那么请不要使用'--',而是将数据转化到ordsets,然后调用ordsets:substract/2. * 对于binary相关操作可以进行binary优化(bin_opt_info编译选项)代码框架: * f(<<Pattern1,...,Rest/bits>>,...) -> ... % Rest is not used here f(Rest,...); f(<<Pattern2,...,Rest/bits>>,...) -> ... % Rest is not used here f(Rest,...); ... f(<<>>, ...) -> ReturnValue. * 调用lists:flatten/1可以将list扁平化,这个操作代价很大,比'++'还要昂贵。下面这些时候我们可以避免: 将数据发送给port时 调用list_bo_binary/1和iolist_to_binary前 * 小的函数可以让您方便的找出错误的函数和代码 * 不要在同一行出现相同的符号 20 some_fun() -> 21 L = [{key1, v1}, {key2, [some_record#v21, v22]}], 22 ... 编译时,会提示line 21 '[' 语法错误, 因为21行有多个 '[' ,所以这个bug不能准确定位,你需要花时间去排查代码。 好的做法是: 20 some_fun() -> 21 L = [{key1, v1}, 22 {key2, [some_record#v21, v22]} 23 ], ... 这样,编译其会提示你 line 22 '[' 语法错误,你很开就知道是那个地方错了。 * 使用 CTRL + \ 或 init:stop(), 可以退出Erlang, 使用CTRL + G 及 CTRL + C 弹出菜单选项,可以选择是否退出Erlang。 其中CTRL + G可以用来连接其他的shell, CTRL + C可以查看其他一些系统信息 Ctrl + C abort 是野蛮的退出方式 * use "open_port({fd,0,2}, [out])" make erlang program write standard error to unix system * If you don't run experiments before you start designing a new system, your entire system will be an experiment! * standard data structure desc: Module Description sets sets, i.e. a collection of unique elements. gb_sets sets, but based on a general balanced data structure gb_tree a general balanced tree dict maps, also called associative arrays ets hash tables and ordered sets (trees) dets on-disk hash tables Suggestion: elments count: 0 - 100 | 100 - 10000 | 10000 - our select : list | ets | gb_tree * 通过code:clash/0 检测代码中是否有module冲突现象(sticky) * epmd -d -d 启动 epmd 可以查看erlang node之间的通讯 * 将正常的逻辑代码和错误处理代码分离,发生错误时,尽管错误。由另一个错误处理模块进行处理 * 类似于操作系统,我们的程序也可以分为kernel 和 user 两层, 对于kernel绝对不能出现错误, 对于user可以出现错误,进行恢复 * process顶层loop涉及的代码及函数,最好在一个module中实现 * process 的register name和module名称一致, 便于寻找代码 * 每个process具有一个单一的角色,比如:supervisor 用来进行错误恢复, work 工作者,可以出现错误, trusted worker 不会出现错误 * 通过函数调用可以实现的功能,就不要使用sever实现(如gen_server, 及类似的loop 实现) * 给消息加一个tag,在发生错误的时候,可以定位到消息,同时也有利于程序的稳健 * 在消息循环中,对于unknown的消息,请调用lib:flush_receive/0 将其清除,减轻process msg queue的长度 * server中总是书写尾递归的循环 * 尽量使用record, 而不是原始的tuple来表现数据结构, 在使用record时,使用select match: #person{name = Name, age = Age} = Person * 对于返回值,最好也添加一个tag,用来说明返回值类型,或者执行成功与否 * 尽可能少的使用catch和try,在erlang程序中,不推荐主动捕获异常。只有当我们的逻辑特别复杂,我们可以使用throw来返回数据,使用catch来获取返回值。 * 当然程序与外界交互,外界数据不可靠时,需要使用catch和try * 慎重使用process dictory, 当你使用get/1, put/1时,你的应用会具有很大的slide effect。可以通过加入一个新的参数来保存原本需要存储到process dictory中数据 * 如果不想使自己糊涂,请不要使用import * 使用export时,将功能类似的接口组合在一起,并添加合理的注视,这样你的接口更清晰,别人使用起来更方便 * 不要书写嵌套太深的代码 * 不要书写太长的module * 不要书写太长的函数 * 每行代码不能太长 * 避免使用 "_" 匿名变量,请为每个变量选择有意义的名称,如够某个变量暂时不使用,请以下划线 "_" 开始 * {error, enfile} enfile error in socket 是以为内linux系统中 ulimit 限制, 在root下修改:ulimit -n 25000 * {error, enotconn} 表示socket已经关闭 * 在erlang开发时,慎重使用macro,因为erlang的single assign的缘故,同时调用某个marco,而macro又定义了某个变量,可能导致badmatch错误。 比如: -define(ADDLINEINFO1(F), ( begin Str1 = lists:concat(["[Mod:", ?MODULE, " Line:", ?LINE, "]"]), Str1 ++ F end )). -define(WARN(Log, F, D), log4erl:warn(Log, ?ADDLINEINFO(F), D)). 如果连续使用 WARN, 会出现此错误 * erlang中可以定义很多环境变量: ERL_MAX_ETS_TABLES 设置最大的ets数目 默认1400 ERL_MAX_PORTS erlang最大的port数目 默认1024 * .app文件中的start_phases, 选项既可以用来作为include applications之间的同步启动,也可以用来对单个application进行分布启动。 顺序如下 包含included app: application:start(prim_app) => prim_app_cb:start(normal, []) => prim_app_cb:start_phase(init, normal, []) => prim_app_cb:start_phase(go, normal, []) => incl_app_cb:start_phase(go, normal, []) ok 无included app: application:start(prim_app) => prim_app_cb:start(normal, []) => prim_app_cb:start_phase(init, normal, []) => prim_app_cb:start_phase(go, normal, []) ok * 任何时候,都要重视函数的返回值,通过match确保您的预期,如果发生错误,那么就大胆的表达出来。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-04-24
well done .再接再厉
|
|
返回顶楼 | |
发表时间:2009-04-25
CTRL + \ 触发SIGQUIT, 调用erl_exi退出更野蛮。
|
|
返回顶楼 | |
发表时间:2009-04-25
Good Job!收藏了
|
|
返回顶楼 | |
发表时间:2009-04-25
mryufeng 写道 CTRL + \ 触发SIGQUIT, 调用erl_exi退出更野蛮。
q().看上去少其实也要敲五个字母 |
|
返回顶楼 | |
发表时间:2009-04-27
引用 * 对于不同的数据类型,使用不同的size函数:tuple_size/1, byte_size/1, bit_size/1 这一点是否有具体原因? 看beam代码感觉size/1和tuple_size/1、byte_size/1的效率应该没什么差别,都需要判断一次具体类型,长度计算方法是一样的,实际测试也没发现有差异。 R13A代码中: * size/1实现在erts/emulator/beam/erl_bif_guard.c@L186 * bit_size/1实现在上面文件的L207 * byte_size/1实现在上面文件的L239 * tuple_size/1实现在erts/emulator/beam/bif.c@L2079 |
|
返回顶楼 | |
发表时间:2009-04-27
chaoslawful 写道 引用 * 对于不同的数据类型,使用不同的size函数:tuple_size/1, byte_size/1, bit_size/1 这一点是否有具体原因? 看beam代码感觉size/1和tuple_size/1、byte_size/1的效率应该没什么差别,都需要判断一次具体类型,长度计算方法是一样的,实际测试也没发现有差异。 R13A代码中: * size/1实现在erts/emulator/beam/erl_bif_guard.c@L186 * bit_size/1实现在上面文件的L207 * byte_size/1实现在上面文件的L239 * tuple_size/1实现在erts/emulator/beam/bif.c@L2079 效率应该是不是最大的考虑 主要是从名字上让程序更容易明白。 |
|
返回顶楼 | |
发表时间:2009-04-27
chaoslawful 写道 引用 * 对于不同的数据类型,使用不同的size函数:tuple_size/1, byte_size/1, bit_size/1 这一点是否有具体原因? 看beam代码感觉size/1和tuple_size/1、byte_size/1的效率应该没什么差别,都需要判断一次具体类型,长度计算方法是一样的,实际测试也没发现有差异。 R13A代码中: * size/1实现在erts/emulator/beam/erl_bif_guard.c@L186 * bit_size/1实现在上面文件的L207 * byte_size/1实现在上面文件的L239 * tuple_size/1实现在erts/emulator/beam/bif.c@L2079 CTRL \ 和 q()推出的方式差别很大. q()是调用init:stop会做模块的反初始化, 比如说mnesia不做这一步就可能数据损害。 |
|
返回顶楼 | |
发表时间:2009-05-07
第一句话应该是“确保”吧?
|
|
返回顶楼 | |