檢視原始碼 cprof - 呼叫計數分析器

cprof 是一個分析工具,可用來了解系統中不同函式被呼叫的頻率。

cprof 使用類似於本地呼叫追蹤的斷點,但包含計數器,來收集分析資料。因此,不需要對任何要分析的模組進行特殊編譯。

cprof 會以總呼叫次數降序的方式呈現所有分析過的模組,並且針對每個模組,也會以呼叫次數降序的方式呈現所有分析過的函式。可以指定呼叫次數限制,以篩選掉所有低於此限制的函式。

分析的步驟如下:

  • cprof:start/* - 開始分析,並將指定函式的呼叫計數器歸零,方法是在這些函式上設定呼叫計數斷點。

  • Mod:Fun() - 執行要分析的程式碼。

  • cprof:pause/* - 暫停指定函式的呼叫計數器。這可以最大限度地減少在背景或 shell 中執行的程式碼所造成的影響。當呼叫計數器「達到」主機機器字組大小的上限時,計數器會自動暫停。對於 32 位元主機,最大計數器值為 2,147,483,647。

  • cprof:analyse/* - 收集呼叫計數器並計算結果。

  • cprof:restart/* - 將指定函式的呼叫計數器從零重新開始。可以用於收集一組新的計數器,而無需停止並開始呼叫計數分析。

  • cprof:stop/0..3 - 透過移除指定函式的呼叫計數斷點來停止分析。

函式可以指定為系統中的所有函式、單一模組中的所有函式、一個函式的所有 arity、一個函式,或所有尚未載入的模組中的所有函式。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,因此在分析期間系統中沒有執行其他工作。