プロファイルの採取

プロファイルの採取

OCaml プログラムに対して2種類のプロファイルのとりかたがある:

  1. バイトコードの場合、実行回数を取得する
  2. ネイティブコードの場合、本物のプロファイルを取得する

バイトコードの場合

ocamlcpocamlprof でバイトコードのプロファイルを計測する。 例を示す:

$ ocamlcp -p a graphics.cma graphtest.ml -o graphtest
$ ./graphtest
$ ocamlprof graphtest.ml

Random.self_init ();;
Graphics.open_graph " 640x480";;

let rec iterate r x_init i =
        (* 12820000 *) if i == 1 then (* 25640 *) x_init
        else
                (* 12794360 *) let x = iterate r x_init (i-1) in
                r *. x *. (1.0 -. x);;

for x = 0 to 640 do
        (* 641 *) let r = 4.0 *. (float_of_int x) /. 640.0 in
        for i = 0 to 39 do
                (* 25640 *) let x_init = Random.float 1.0 in
                let x_final = iterate r x_init 500 in
                let y = int_of_float (x_final *. 480.) in
                Graphics.plot x y
        done
done;;

(* nnn *) というコメントが ocamlprof が付け加えたもので、 そのコード部分が何回呼ばれたかを示している。

ネイティブコードの場合

ネイティブコードのプロファイルについては、 使っている OS のプロファイラにネイティブ対応している。 Linux の場合 gprof を使う。

ネイティブコードのプロファイルの実例を示すにあたり、 エラトステネスの篩(元コード) で最初の3000個の素数を求める。 このプログラムはチュートリアルの範囲外のテクニックである stream と camlp4 を使っている。

let rec filter p = parser
  [< 'n; s >] -> if p n then [< 'n; filter p s >] else [< filter p s >]

let naturals =
  let rec gen n = [< 'n; gen (succ n) >] in gen 2

let primes =
  let rec sieve = parser
    [< 'n; s >] -> [< 'n; sieve (filter (fun m -> m mod n <> 0) s) >]
  in
  sieve naturals
  ;;

for i = 1 to 3000 do
  ignore (Stream.next primes)
done

コンパイラに対して gprof のプロファイル情報を含めるよう、 コンパイル時に ocamlopt-p オプションを積む。

$ ocamlopt -p -pp "camlp4o pa_extend.cmo" -I +camlp4 sieve.ml -o sieve

普通にプログラムを走らせると、 プロファイル情報が gmon.out という gprof 用のファイルに出力される。

$ gprof ./sieve
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total
 time   seconds   seconds    calls   s/call   s/call  name
 10.86      0.57     0.57     2109     0.00     0.00  sweep_slice
  9.71      1.08     0.51     1113     0.00     0.00  mark_slice
  7.24      1.46     0.38  4569034     0.00     0.00  Sieve__code_begin
  6.86      1.82     0.36  9171515     0.00     0.00  Stream__set_data_140
  6.57      2.17     0.34 12741964     0.00     0.00  fl_merge_block
  6.29      2.50     0.33  4575034     0.00     0.00  Stream__peek_154
  5.81      2.80     0.30 12561656     0.00     0.00  alloc_shr
  5.71      3.10     0.30     3222     0.00     0.00  oldify_mopup
  4.57      3.34     0.24 12561656     0.00     0.00  allocate_block
  4.57      3.58     0.24  9171515     0.00     0.00  modify
  4.38      3.81     0.23  8387342     0.00     0.00  oldify_one
  3.81      4.01     0.20 12561658     0.00     0.00  fl_allocate
  3.81      4.21     0.20  4569034     0.00     0.00  Sieve__filter_56
  3.62      4.40     0.19     6444     0.00     0.00  empty_minor_heap
  3.24      4.57     0.17     3222     0.00     0.00  oldify_local_roots
  2.29      4.69     0.12  4599482     0.00     0.00  Stream__slazy_221
  2.10      4.80     0.11  4597215     0.00     0.00  darken
  1.90      4.90     0.10  4596481     0.00     0.00  Stream__fun_345
  1.52      4.98     0.08  4575034     0.00     0.00  Stream__icons_207
  1.52      5.06     0.08  4575034     0.00     0.00  Stream__junk_165
  1.14      5.12     0.06     1112     0.00     0.00  do_local_roots

[ etc. ]

このプログラムではガベージコレクタが時間を多く消費していることがわかる。 (驚くなかれ、このプログラムはエレガントだが最適化はしていない。 配列やループを使った方がはるかに速く動作するだろう)