length
在某些情况下可用模式匹配替代
1
2
3
4
| test(L) when length(L) >= 3 ->
do_something;
test(_) ->
skip.
|
1
2
3
4
| test([_,_,_|Tail] = L) ->
do_something;
test(_) ->
skip.
|
一个细微的区别是length(L)如果L是一个不正确的列表,而第二个代码片段中的模式接受一个不正确的列表
size
- 返回元祖和二进制数据的大小。使用BIF函数tuple_size/1、byte_size/1会更高效
- 由于使用BIF编译器和运行时系统更多的优化机会。另一个优势是BIF为透析器提供了更多的类型信息。
运算符”–”
- ”–“运算符的复杂度与其操作数长度的乘积成正比。这意味着如果运算符的两个操作数都是长列表,则运算符非常慢
- 使用STDLIB中的模块ordsets
1
2
3
4
5
6
| HugeSet1 = ordsets:from_list(HugeList1),
HugeSet2 = ordsets:from_list(HugeList2),
ordsets:subtract(HugeSet1, HugeSet2)
%% 或者
Set = gb_sets:from_list(HugeList2),
[E || E <- HugeList1, not gb_sets:is_element(E, Set)]
|
erlc 编译
- -S 生成汇编文件
- +bin_opt_info 输出二进制优化
列表推导
列表推导编译后会优化成尾递归,编译时加-S参数可以看到汇编文件,查看具体的执行流程
1
2
3
4
5
| [Expr(E) || E <- List]
%% 编译后转化成本地函数
'lc^0'([E|Tail], Expr) ->
[Expr(E)|'lc^0'(Tail, Expr)];
'lc^0'([], _Expr) -> [].
|
lists:flatten/1
避免使用”lists:flatten/1”,该方法会构造一个全新的列表,甚至比”++”运算符还要糟糕
- 将数据发送到端口时。端口理解深度列表,因此在将列表发送到端口之前,没有理由将其扁平化。
- 调用接受深列表的BIF时,如”list_to_binary/1”或”iolist_to_binary/1”
- 当您知道您的列表只有一级深度时,可以使用”lists:append/1”
erlang:now 和 erlang:timestamp 区别
erlang:now在获取时间的时候需要上erts_timeofday_mtx锁,而os:timestamp则简单的调用sys_gettimeofday函数来获取,并且在linux下gettimeofday这个系统调用由于使用频度非常高,系统已经将之vdso化了,也就说去取这个时间等同于读取一个字长的内存的代价而已。
获取进程调用栈
1
| file:write_file("stack.info", io_lib:format("~s~n", [element(2, process_info(Pid, backtrace))]))
|
性能排查demo
1
2
3
4
5
6
7
8
9
10
11
12
| cprof:start(),R=calendar:day_of_the_week(1896,4,27),cprof:pause(),R.
cprof:analyse(calendar).
cprof:stop().
fprof:start(),
fprof:apply(M, F, A),
fprof:profile(),
fprof:analyse([{dest, "fprof.analysis"},{sort,own}]),
fprof:stop().
|
记一次cup占用过高问题排查 (50% 降低至 20%)
查找cup占用较高的进程 etop 模块
1
2
3
4
5
6
7
8
9
10
11
| % cup占用
spawn(fun() -> etop:start([{interval, 10}, {sort, runtime}]) end).
% 消息队列
spawn(fun() -> etop:start([{interval, 10}, {sort, msg_q}]) end).
% 内存
spawn(fun() -> etop:start([{interval, 10}, {sort, memory}]) end).
% 停止
etop:stop()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| ========================================================================================
'node name' 02:22:21
Load: cpu 1 Memory: total 1274317 binary 14109
procs 1596 processes 1009762 code 15398
runq 0 atom 791 ets 190380
Pid Name or Initial Func Time Reds Memory MsgQ Current Function
----------------------------------------------------------------------------------------
<6469.1478.0> mod:init/1 '-' 272281 2928152 0 gen_server:loop/7
<6469.624.0> mod:init/1 '-' 196335 429128 0 gen_server:loop/7
<6469.775.0> mod:init/1 '-' 196320 1120272 0 gen_server:loop/7
<6469.773.0> mod:init/1 '-' 195253 427848 0 gen_server:loop/7
<6469.626.0> mod:init/1 '-' 194656 1117712 0 gen_server:loop/7
<6469.31963.12>mod:init/1 '-' 190817 690864 0 gen_server:loop/7
<6469.6869.15> mod:init/1 '-' 190817 690864 0 gen_server:loop/7
<6469.7847.2> mod:init/1 '-' 190814 690864 0 gen_server:loop/7
<6469.1168.8> mod:init/1 '-' 190278 427848 0 gen_server:loop/7
<6469.1217.0> mod:init/1 '-' 190278 427848 0 gen_server:loop/7
========================================================================================
|
对高占用进程进行性能分析 eprof 模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
%% 开始对特定进程分析
Pids = [pid(0,1478,0)].
eprof:start().
eprof:profile(Pids).
%% 停止
eprof:stop_profiling().
%% 在执行eprof:analyze()前执行设置日志文件,可将分析结果保存到文件中
eprof:log("pid1478.analyze").
%% 分析结果
eprof:analyze().
|
- 对分析结果查看,得知部分”ets:select/2”消耗时间较长,于是对查询结果加入缓存(k/v cache),下次查询直接冲缓存中获取
- k/v cache 可独立进程,将系统中的缓存集中存放,避免各自进程独自处理存放,加大内存开销
加入缓存处理后,cup占用依旧高居不下
于是对进程进行分析发现,存在许多废弃没有关闭的进程。对进程代码修正,加入自动关闭进程功能,cup占用就降低了。
在监督树下的用 “supervisor:terminate_child(Sup, ID)” 关闭
关闭后需清理监督树的子进程监督信息 “supervisor:delete_child(Sup, ID)” (看源码 terminate_child 后是有清除数据的,但是没起作用,待查)
非监督树下的进程返回 {stop, normal, State}
总结
- 对耗时的数据查询,加入缓存处理
- 使用全局缓存,避免同样的数据重复存储,占用内存
- 进程都需有关闭处理,废弃闲置进程需关闭