查看源代码 cprof - 调用计数分析器
cprof
是一个分析工具,可以用来了解系统中不同函数被调用的频率。
cprof
使用类似于本地调用跟踪的断点,但包含计数器,以收集分析数据。因此,无需对任何要分析的模块进行特殊编译。
cprof
以总调用次数降序的方式显示所有被分析的模块,并且对于每个模块,也以调用次数降序的方式显示所有被分析的函数。可以指定调用次数限制以过滤掉低于限制的所有函数。
分析按以下步骤进行
cprof:start/*
- 通过在指定的函数上设置调用计数断点,开始分析并初始化它们的调用计数器为零。Mod:Fun()
- 运行要分析的代码。cprof:pause/*
- 暂停指定函数的调用计数器。 这可以最大限度地减少在后台或 shell 中运行的代码的影响。当调用计数器“达到”主机机器字长的上限时,它们会自动暂停。对于 32 位主机,最大计数器值为 2,147,483,647。cprof:analyse/*
- 收集调用计数器并计算结果。cprof:restart/*
- 将指定函数的调用计数器从零重新开始。 可以用于收集一组新的计数器,而无需停止和启动调用计数分析。cprof:stop/0..3
- 通过从指定函数中删除调用计数断点来停止分析。
函数可以指定为系统中的所有函数,一个模块中的所有函数,一个函数的所有参数组合,一个函数,或所有尚未加载的模块中的所有函数。BIF 不能进行调用计数跟踪。
分析结果可以是单个模块的,也可以是所有模块的。在任何一种情况下,都可以给出调用计数限制来过滤掉调用计数低于限制的函数。所有模块的分析不包含模块 cprof
本身;分析 cprof
的唯一方法是将其指定为要分析的单个模块。
与其他形式的跟踪相比,调用计数跟踪非常轻量级,因为它不需要生成任何跟踪消息。一些测量表明性能下降约为 10%。
以下部分显示了一些使用 cprof
进行分析的示例。
示例:后台工作
从 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()
之间的命令行时所执行的工作的一部分。
示例:一个模块
从 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()
也显示了与第一个示例中大致相同的后台工作。
示例:在代码中
编写一个模块
-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
,因此在分析期间系统中没有进行其他工作。