2 cprof - 调用次数分析器
cprof 是一款分析工具,可用于了解系统中不同函数被调用的频率。
cprof 使用类似于局部调用跟踪的断点,但包含计数器,以收集分析数据。因此,无需对要分析的任何模块进行特殊编译。
cprof 按总调用次数降序显示所有经过分析的模块,并为每个模块显示所有经过分析的函数,同样按调用次数降序排列。可以指定调用次数限制,以筛选掉低于限制的所有函数。
分析过程如下所示:
- cprof:start/0..3
- 通过为指定函数设置调用次数断点,开始分析,并为这些函数的调用计数器清零。
- Mod:Fun()
- 运行要分析的代码。
- cprof:pause/0..3
- 暂停指定函数的调用计数器。这将最大程度地减少后台或 shell 中运行的代码对分析的影响,这些代码会干扰分析。当调用计数器达到主机机器字长大小的“上限”时,调用计数器会自动暂停。对于 32 位主机,最大计数器值为 2147483647。
- cprof:analyse/0..2
- 收集调用计数器并计算结果。
- cprof:restart/0..3
- 为指定函数的调用计数器从零重新开始。可用于收集新的计数器集,而无需停止和启动调用次数分析。
- cprof:stop/0..3
- 通过从指定函数中删除调用次数断点来停止分析。
函数可以指定为系统中的所有函数、一个模块中的所有函数、一个函数的所有元数、一个函数或所有尚未加载的模块中的所有函数。目前,BIF 无法进行调用次数跟踪。
分析结果可以是所有模块的,也可以是单个模块的。在这两种情况下,都可以给出调用次数限制,以筛选掉调用次数低于限制的函数。所有模块的分析结果**不**包含cprof 模块本身,只能通过将它指定为要分析的单个模块来分析它。
与其他形式的跟踪相比,调用次数跟踪非常轻量级,因为不需要生成任何跟踪消息。一些测量表明性能下降约为 10%。
以下部分展示了使用cprof 进行分析的一些示例。另请参见cprof(3)。
2.1 示例:后台工作
从 Erlang shell 中
1> cprof:start(), cprof:pause(). % Stop counters just after start 8492 2> cprof:analyse(). {539, [{shell,155, [{{shell,prep_check,1},55}, {{shell,used_records,4},45}, {{shell,used_records,1},45}, {{shell,used_record_defs,2},1}, {{shell,record_defs,2},1}, {{shell,record_bindings,2},1}, {{shell,exprs,7},1}, {{shell,expr,4},1}, {{shell,expand_records,2},1}, {{shell,check_command,2},1}, {{shell,apply_fun,3},1}, {{shell,'-exprs/7-lc$^0/1-0-',1},1}, {{shell,'-eval_loop/3-fun-0-',3},1}]}, %% Information about many modules omitted. . . . %% Here is the last part. {erts_internal,2,[{{erts_internal,trace_pattern,3},2}]}, {otp_internal,1,[{{otp_internal,obsolete,3},1}]}, {maps,1,[{{maps,from_list,1},1}]}, {erl_internal,1,[{{erl_internal,bif,3},1}]}]} 3> cprof:analyse(cprof). {cprof,3,[{{cprof,tr,2},2},{{cprof,pause,0},1}]} 4> cprof:stop(). 8586
示例展示了 shell 仅为解释第一个命令行而执行的一些后台工作。
本示例捕获的是 shell 在解释命令行时所做的工作的一部分,这些工作发生在实际调用cprof:start() 和cprof:analyse() 之间。
2.2 示例:单个模块
从 Erlang shell 中
1> cprof:start(),R=calendar:day_of_the_week(1896,4,27),cprof:pause(),R. 1 2> cprof:analyse(calendar). {calendar,9, [{{calendar,last_day_of_the_month1,2},1}, {{calendar,last_day_of_the_month,2},1}, {{calendar,is_leap_year1,1},1}, {{calendar,is_leap_year,1},1}, {{calendar,dy,1},1}, {{calendar,dm,1},1}, {{calendar,df,2},1}, {{calendar,day_of_the_week,3},1}, {{calendar,date_to_gregorian_days,3},1}]} 3> cprof:stop(). 8648
示例告诉我们“Aktiebolaget LM Ericsson & Co”是在星期一注册的(因为第一个命令的返回值为 1),并且calendar 模块需要 9 个函数调用才能计算出该日期。
在本示例中使用cprof:analyse() 也显示了与第一个示例中大约相同的后台工作。
2.3 示例:在代码中
编写模块
-module(sort). -export([do/1]). do(N) -> cprof:stop(), cprof:start(), do(N, []). do(0, L) -> R = lists:sort(L), cprof:pause(), R; do(N, L) -> do(N-1, [rand:uniform(256)-1 | L]).
从 Erlang shell 中
1> c(sort). {ok,sort} 2> rand:seed(default, 42), ok. ok. 3> sort:do(1000). [0,0,0,1,1,1,1,2,2,3,3,4,4,4,4,5,5,5,6,6,6,6,7,7,7,7,7,8,8|...] 4> cprof:analyse(). {13180, [{lists,6173, [{{lists,rmerge3_1,6},1045}, {{lists,rmerge3_2,6},977}, {{lists,split_1,5},652}, {{lists,merge3_1,6},579}, {{lists,merge3_2,6},577}, {{lists,rmerge3_12_3,6},511}, {{lists,split_1_1,6},347}, {{lists,merge3_12_3,6},310}, {{lists,rmerge3_21_3,6},282}, {{lists,merge3_21_3,6},221}, {{lists,merge2_1,4},154}, {{lists,merge2_2,5},138}, {{lists,reverse,2},106}, {{lists,rmerge2_2,5},87}, {{lists,rmergel,2},81}, {{lists,rmerge2_1,4},75}, {{lists,mergel,2},28}, {{lists,keyfind,3},2}, {{lists,sort,1},1}]}, {rand,5000, [{{rand,uniform_s,2},1000}, {{rand,uniform,1},1000}, {{rand,seed_put,1},1000}, {{rand,seed_get,0},1000}, {{rand,exsss_uniform,2},1000}]}, {erlang,1004, [{{erlang,put,2},1000}, {{erlang,trace_pattern,3},2}, {{erlang,ensure_tracer_module_loaded,2},2}]}, {sort,1001,[{{sort,do,2},1001}]}, {erts_internal,2,[{{erts_internal,trace_pattern,3},2}]}]} 5> cprof:stop(). 12625
示例展示了lists:sort/1 的工作原理的一些细节。它在lists 模块中使用了 6173 个函数调用来完成工作。
这次,由于 shell 没有参与启动和停止cprof,因此在分析期间系统中没有进行其他工作。