-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathprofiling.xml
More file actions
1220 lines (997 loc) · 76.8 KB
/
profiling.xml
File metadata and controls
1220 lines (997 loc) · 76.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<chapter id="profiling">
<title>プロファイルを取る</title>
<indexterm><primary>profiling</primary>
</indexterm>
<indexterm><primary>cost-centre profiling</primary></indexterm>
<para>GHCには時間及び空間のプロファイルをとるためのシステムが付属している。このシステムの目的は、「なぜ私のプログラムはこんなに遅いのか」「なぜ私のプログラムはこんなに沢山のメモリを使うのか」といった疑問に答えられるようにすることである。</para>
<para>プログラムのプロファイルをとるのは三つの段階からなる。</para>
<orderedlist>
<listitem>
<para>プログラムをプロファイル用にコンパイルする。これには、<literal>-prof</literal>オプションと、多くの場合、自動注釈を付けるオプションの一つを付ける。<literal>-fprof-auto</literal>が最も一般的である<footnote><para><option>-fprof-auto</option>は、GHC 7.4.1より前は<option>-auto-all</option><indexterm><primary><literal>-auto-all</literal></primary></indexterm>という名前だった。</para></footnote>。</para>
<para><literal>cabal</literal>で外部ライブラリを使っているなら、それらのライブラリをプロファイル対応付きで再インストールする必要があるかもしれない。これは典型的には<literal>cabal install -p <replaceable>package</replaceable> --reinstall</literal>とする。</para>
</listitem>
<listitem>
<para>プログラムをプロファイル用にコンパイルしたら、次はプロファイルを生成するためにそれを走らせる必要がある。例えば、単純な時間プロファイルはプログラムを<option>+RTS -p</option><indexterm><primary><option>-p</option></primary><secondary>RTS option</secondary></indexterm>付きで走らせることによって生成できる。これは<literal><replaceable>prog</replaceable>.prof</literal>という名前のファイルを生成する。ただし<replaceable>prog</replaceable>はあなたのプログラムの名前である(Windows上なら<literal>.exe</literal>拡張子を除いたもの)。</para>
<para>生成できるプロファイルには沢山の種類があり、異なるRTSオプションによって選択できる。この章の残りの部分ではこの色々な種類のプロファイルを記述する。プロファイルの中には、プログラムを実行した後にさらに別のツールで処理することを必要とするものもある。</para>
</listitem>
<listitem>
<para>生成されたプロファイル情報を調べ、その情報を使ってプログラムを最適化し、必要に応じて繰り返す。</para>
</listitem>
</orderedlist>
<sect1 id="cost-centres">
<title>コスト集約点とコスト集約点スタック</title>
<para>GHCのプロファイルシステムでは<firstterm>コスト</firstterm>は<firstterm>コスト集約点</firstterm>(cost centre)に割り当てられる。コストとは式を評価するのに必要な時間と空間(メモリ)のことである。コスト集約点はプログラム上の注釈で、一定の範囲の式を支配する。注釈の付いた式が発生させたコストは全て、それを直接支配するコスト集約点に割り当てられる。さらに、GHCは任意の式についてそれを支配するコスト集約点のスタックを実行時に記憶していて、どこにどれだけコストが掛かったかという情報の付いた呼び出し木を生成する。<!--(訳注: この節では多少無理な翻訳をしているので語句の対応を示す。改善案募集。cost centre: コスト集約点、enclose: 支配、assign: 割り当てる、attribute: 割り当てる/配分する)--></para>
<para>例を一つ見てみよう。</para>
<programlisting>
main = print (fib 30)
fib n = if n < 2 then 1 else fib (n-1) + fib (n-2)
</programlisting>
<para>このプログラムを次のようにコンパイルし、実行する。</para>
<screen>
$ ghc -prof -fprof-auto -rtsopts Main.hs
$ ./Main +RTS -p
121393
$
</screen>
<para>GHCでコンパイルされたプログラムは、<option>-p</option>というRTSオプション付きで実行されると、<filename><replaceable>prog</replaceable>.prof</filename>というファイルを生成する。この場合、ファイルの内容は以下のようなものである。</para>
<screen>
Wed Oct 12 16:14 2011 Time and Allocation Profiling Report (Final)
Main +RTS -p -RTS
total time = 0.68 secs (34 ticks @ 20 ms)
total alloc = 204,677,844 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
fib Main 100.0 100.0
individual inherited
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 102 0 0.0 0.0 100.0 100.0
CAF GHC.IO.Handle.FD 128 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 120 0 0.0 0.0 0.0 0.0
CAF GHC.Conc.Signal 110 0 0.0 0.0 0.0 0.0
CAF Main 108 0 0.0 0.0 100.0 100.0
main Main 204 1 0.0 0.0 100.0 100.0
fib Main 205 2692537 100.0 100.0 100.0 100.0
</screen>
<para>ファイルの最初の部分は、プログラムの名前、オプション、実行時に計測された合計実行時間と合計メモリ確保量を示している。(合計メモリ確保量はある一つの時点においてプログラムが必要とする<emphasis>生存</emphasis>メモリの量とは異なることに注意。後者はヒーププロファイルで量れるが、これについては後で<xref linkend="prof-heap" />内で説明する)</para>
<para>ファイルの二番目の部分は、プログラムの中でコストが高い関数をコスト集約点で分類したものである。この例では、プログラムにはコストの高い関数が一つ(<function>fib</function>)しかなく、これがプログラムの時間と確保量の両方について100%のコストを占めている。</para>
<para>三番目の最後の節はコスト集約点スタックで分類されたプロファイルを表示している。これはプログラムの呼び出し木とだいたい同じである。上記の例では、コストの高い<function>fib</function>の呼び出しが<function>main</function>由来のものであることが明らかになっている。</para>
<para>プログラムの特定の部分の時間と確保量は二種類が表示されている。「individual」はこのコスト集約点スタックに相当する部分のコードが消費したものだけを示す。「inherited」はこのノードの子が消費したものも全て含む。</para>
<para>例を少し変えると、コスト集約点スタックの有用性がより良く分かるようになる。</para>
<programlisting>
main = print (f 30 + g 30)
where
f n = fib n
g n = fib (n `div` 2)
fib n = if n < 2 then 1 else fib (n-1) + fib (n-2)
</programlisting>
<para>このプログラムを前と同じようにコンパイル・実行し、新しいプロファイル結果を見てみる。</para>
<screen>
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 102 0 0.0 0.0 100.0 100.0
CAF GHC.IO.Handle.FD 128 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 120 0 0.0 0.0 0.0 0.0
CAF GHC.Conc.Signal 110 0 0.0 0.0 0.0 0.0
CAF Main 108 0 0.0 0.0 100.0 100.0
main Main 204 1 0.0 0.0 100.0 100.0
main.g Main 207 1 0.0 0.0 0.0 0.1
fib Main 208 1973 0.0 0.1 0.0 0.1
main.f Main 205 1 0.0 0.0 100.0 99.9
fib Main 206 2692537 100.0 99.9 100.0 99.9
</screen>
<para><function>nfib</function>の呼び出しをプログラム中で二回行ったが、時間を食っているのが<function>f</function>経由の呼び出しだということが明らかだ。<literal>main</literal>の<literal>where</literal>節で定義されている関数<literal>f</literal>と<literal>g</literal>は、それぞれ<literal>main.f</literal>と<literal>main.g</literal>という固有のコスト集約点を与えられている。</para>
<para>出力の各列の実際の意味は以下の通りである。</para>
<variablelist>
<varlistentry>
<term>entries</term>
<listitem>
<para>呼び出し木のこの場所に入った回数。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>individual %time</term>
<listitem>
<para>呼び出し木中のこの場所で消費された時間の、総時間に対する割合。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>individual %alloc</term>
<listitem>
<para>この呼び出しでなされたメモリ確保量(プロファイルによる余分は除く)の全体に占める割合。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>inherited %time</term>
<listitem>
<para>呼び出し木中のこの点以下で消費された時間の、プログラムの総実行時間に占める割合。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>inherited %alloc</term>
<listitem>
<para>この呼び出しとその部分呼び出しでなされたメモリ確保量(プロファイルによる余分は除く)の全体に占める割合。</para>
</listitem>
</varlistentry>
</variablelist>
<para>加えて、RTSオプション<option>-P</option><indexterm><primary><option>-P</option></primary></indexterm>を使うと、下記の情報が追加される。</para>
<variablelist>
<varlistentry>
<term><literal>ticks</literal></term>
<listitem>
<para>この集約点に割り当てられた生の時刻信号(tick)の数。上記の<literal>%time</literal>はこの値から得られている。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>bytes</literal></term>
<listitem>
<para>この集約点の支配下でヒープ中に確保されたバイト数。これは上記の<literal>%alloc</literal>の数値の元となるものである。</para>
</listitem>
</varlistentry>
</variablelist>
<para>再帰的関数や相互に再帰的な一群の関数についてはどうだろうか。コストはどこに割り当てられるのか。答えはこうである。GHCは一群の関数が互いに再帰的に呼び合ったという情報は保持するが、時刻と確保の基本プロファイルにおいてはこの情報は表示されず、呼び出しグラフはある方法で平坦化されて木として表示される。すなわち、現在の呼び出しスタックの別の場所に出現する関数を呼び出してもスタックに項目は追加されず、その呼び出しのコストは呼び出し元に集積される。<footnote><para>この方針はGHC 7.4.1で微妙に変わり、これからさらに変わるかもしれないことに注意。意見歓迎。</para></footnote></para>
<sect2 id="scc-pragma"><title>コスト集約点を手動で挿入する</title>
<para>コスト集約点は、単なるプログラム上の注釈である。コンパイラに<option>-fprof-all</option>を指示すると、INLINE指定されていない全ての関数の周りに自動的にコスト集約点が挿入される。しかし、自分でコスト集約点を挿入するのも完全に自由である。</para>
<para>コスト集約点の注釈の構文は以下である。</para>
<programlisting>
{-# SCC "name" #-} <expression>
</programlisting>
<para>ここで、<literal>"name"</literal>は任意の文字列であり、これがこのコスト集約点の名前としてプロファイル出力に現れる。<literal><expression></literal>は任意のHaskellの式である。パース時には<literal>SCC</literal>注釈は右側に可能な限り長く続くように解釈される。(SCCは「Set Cost Centre」(コスト集約点を設定せよ)の意である)。<literal>name</literal>がHaskellの識別子であるなら、二重引用符は省略できる。例を示す。</para>
<programlisting>
{-# SCC my_function #-} <expression>
</programlisting>
<para>SCCをいくつか使ったプログラムの例を示す。</para>
<programlisting>
main :: IO ()
main = do let xs = [1..1000000]
let ys = [1..2000000]
print $ {-# SCC last_xs #-} last xs
print $ {-# SCC last_init_xs #-} last $ init xs
print $ {-# SCC last_ys #-} last ys
print $ {-# SCC last_init_ys #-}last $ init ys
</programlisting>
<para>実行すると、次のようなプロファイルが得られる。</para>
<screen>
COST CENTRE MODULE no. entries %time %alloc %time %alloc
MAIN MAIN 102 0 0.0 0.0 100.0 100.0
CAF GHC.IO.Handle.FD 130 0 0.0 0.0 0.0 0.0
CAF GHC.IO.Encoding.Iconv 122 0 0.0 0.0 0.0 0.0
CAF GHC.Conc.Signal 111 0 0.0 0.0 0.0 0.0
CAF Main 108 0 0.0 0.0 100.0 100.0
main Main 204 1 0.0 0.0 100.0 100.0
last_init_ys Main 210 1 25.0 27.4 25.0 27.4
main.ys Main 209 1 25.0 39.2 25.0 39.2
last_ys Main 208 1 12.5 0.0 12.5 0.0
last_init_xs Main 207 1 12.5 13.7 12.5 13.7
main.xs Main 206 1 18.8 19.6 18.8 19.6
last_xs Main 205 1 6.2 0.0 6.2 0.0
</screen>
</sect2>
<sect2 id="prof-rules">
<title>コストの配分規則</title>
<para>プロファイルを有効にしてプログラムを実行している間、GHCは舞台裏でコスト集約点スタックを管理し、発生したあらゆるコスト(メモリ確保量と時間)をその時点でのコスト集約点に配分する。</para>
<para>機構は単純である。プログラムがSCC注釈の付いた式<literal>{-# SCC c -#} E</literal>を評価するときはいつも、<literal>c</literal>がその時点のスタックにプッシュされ、このスタックへの進入回数に1が加算される。このスタックを保存したり復元したりする必要があることもある。特に、プログラムが<firstterm>サンク</firstterm>(lazyな中断)を作るとき、現在のコスト集約点スタックがサンクに保存され、そのサンクが評価されるときに復元される。こうすることで、コスト集約点スタックはGHCが実行時に実際に使う評価の順番とは独立になる。</para>
<para>関数呼び出しに際しては、GHCは呼ばれる関数に保存されているスタック(これは最上位の関数については空)を取り出し、それをその時点のスタックの<emphasis>後ろに連結</emphasis>する。このとき、その時点のスタックのprefixであるようなprefixは全て無視する。</para>
<para>遅延された計算すなわちサンクが、作成された時にその時点のスタックを捕捉することは先に言及した。最上位のサンクについてはどうか?これらはプログラムがコンパイルされたときに「作成」されるのだから、どんなスタックを与えるべきか?最上位のサンクを指す述語はCAF(Constant Applicative Form; 定作用形)である。GHCはモジュール中の全てのCAFに単一のコスト集約点<literal>M.CAF</literal>を与える。ここで<literal>M</literal>はそのモジュールの名前である。それぞれのCAFに異なるスタックを与えることも可能であり、<option>-fprof-cafs</option><indexterm><primary><option>-fprof-cafs</option></primary></indexterm>オプションを使う。<option>-ffull-laziness</option>(<option>-O</option>以上でデフォルトである)を使ってコンパイルする場合、関数本体にある定数が最上位へと持ち上げられてCAFになるので、これが特に便利である。これらのCAFが何に対応するかを知るには、おそらくCore(<option>-ddump-simpl</option>)を参照する必要があるだろう。</para>
</sect2>
</sect1>
<sect1 id="prof-compiler-options">
<title>プロファイルについてのコンパイルオプション</title>
<indexterm><primary>profiling</primary><secondary>options</secondary></indexterm>
<indexterm><primary>options</primary><secondary>for profiling</secondary></indexterm>
<variablelist>
<varlistentry>
<term>
<option>-prof</option>:
<indexterm><primary><option>-prof</option></primary></indexterm>
</term>
<listitem>
<para>プロファイルシステムを使うには、<emphasis>全ての</emphasis>モジュールが<option>-prof</option>オプション付きでコンパイルされ、<option>-prof</option>オプションつきでリンクされていなければならない。ソース中の全ての<literal>SCC</literal>注釈が意味を持つようになる。</para>
<para><option>-prof</option>オプションが与えられていないとき、<literal>SCC</literal>は無視される。このため、<literal>SCC</literal>を満載したコードを変更せずにコンパイルできる。</para>
</listitem>
</varlistentry>
</variablelist>
<para>この他にもいくつかプロファイルに関連したコンパイルオプションがある。これらは<option>-prof</option>と<emphasis>併用</emphasis>すること。これらのオプションは全てのモジュールで一斉に使う必要はない。</para>
<variablelist>
<varlistentry>
<term>
<option>-fprof-auto</option>:
<indexterm><primary><option>-fprof-auto</option></primary></indexterm>
</term>
<listitem>
<para>INLINE指定されていない<emphasis>全て</emphasis>の束縛に、エクスポートされているか、最上位か局所的かにかかわらず、<function>SCC</function>注釈を自動的に与える。INLINE関数に付属したコスト集約点が欲しい場合は手動で付け加えなければならない。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fprof-auto-top</option>:
<indexterm><primary><option>-fprof-auto-top</option></primary></indexterm>
<indexterm><primary>cost centres</primary><secondary>automatically inserting</secondary></indexterm>
</term>
<listitem>
<para>INLINE指定されていない全ての最上位の束縛に、<function>SCC</function>注釈を自動的に加える。INLINE関数に付属したコスト集約点が欲しい場合は手動で付け加えなければならない。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fprof-auto-exported</option>:
<indexterm><primary><option>-fprof-auto-top</option></primary></indexterm>
<indexterm><primary>cost centres</primary><secondary>automatically inserting</secondary></indexterm>
</term>
<listitem>
<para>INLINE指定されていない全てのエクスポートされた関数に、<function>SCC</function>注釈を自動的に加える。INLINE関数に付属したコスト集約点が欲しい場合は手動で付け加えなければならない。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fprof-auto-calls</option>:
<indexterm><primary><option>-fprof-auto-calls</option></primary></indexterm>
</term>
<listitem>
<para>全ての<emphasis>呼び出し地点</emphasis>に自動的に<literal>SCC</literal>注釈を加える。これはスタックトレースを生成する目的でプロファイルを使っている場合に特に便利である。詳細は、<literal>Debug.Trace</literal>モジュールの<literal>traceStack</literal>かRTSフラグ<literal>-xc</literal>(<xref linkend="rts-options-debugging" />)を見よ。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fprof-cafs</option>:
<indexterm><primary><option>-fprof-cafs</option></primary></indexterm>
</term>
<listitem>
<para>一つのモジュール内の全てのCAFのコストは通常一つの「大きな」CAFというコスト集約点に割り当てられる。このオプションを使うと、全てのCAFが独自のコスト集約点を持つようになる。「他のことがうまくいかなかったら」試すオプションである。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fno-prof-auto</option>:
<indexterm><primary><option>-no-fprof-auto</option></primary></indexterm>
</term>
<listitem>
<para>既に指定された<option>-fprof-auto</option>、<option>-fprof-auto-top</option>、<option>-fprof-auto-exported</option>オプションを無効にする。
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fno-prof-cafs</option>:
<indexterm><primary><option>-fno-prof-cafs</option></primary></indexterm>
</term>
<listitem>
<para>既に指定された<option>-fprof-cafs</option>オプションを無効にする。
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-fno-prof-count-entries</option>:
<indexterm><primary><option>-fno-prof-count-entries</option></primary></indexterm>
</term>
<listitem>
<para>このモジュールについて、実行時に関数が何回進入されたかの情報(時間プロファイルの「entries」列)を収集しないようGHCに指示する。正しい進入回数を管理する必要がないときはGHCがより攻撃的な最適化を行なえるので、このオプションはプロファイルされるコードの実行を高速化する傾向がある。このオプションは進入回数に興味がないときに便利である(たとえばヒーププロファイルだけを意図している場合)。
</para>
</listitem>
</varlistentry>
</variablelist>
</sect1>
<sect1 id="prof-time-options">
<title>時間及び確保量のプロファイルを取る</title>
<para>時間及び確保量のプロファイルを生成するには、コンパイルされたプログラムを実行する際に以下のいづれかのRTSオプションを渡せば良い。(RTSオプションは通常と同じく<literal>+RTS...-RTS</literal>の間になければならない)</para>
<variablelist>
<varlistentry>
<term>
<option>-p</option>または<option>-P</option>または<option>-pa</option>:
<indexterm><primary><option>-p</option></primary></indexterm>
<indexterm><primary><option>-P</option></primary></indexterm>
<indexterm><primary><option>-pa</option></primary></indexterm>
<indexterm><primary>time profile</primary></indexterm>
</term>
<listitem>
<para><option>-p</option>オプションは標準的な<emphasis>時間プロファイル</emphasis>の報告を生成する。結果は<filename><replaceable>program</replaceable>.prof</filename>というファイルに出力される。</para>
<para><option>-P</option>はより詳細なプロファイル(実際の時間と確保量のデータを含む)を生成する。(あまり使われない)</para>
<para><option>-pa</option>オプションは最も詳細な報告を生成する。実際の時間と確保量データに加えて、全てのコスト集約点を含んだものである。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-V<replaceable>secs</replaceable></option>
<indexterm><primary><option>-V</option></primary><secondary>RTS
option</secondary></indexterm></term>
<listitem>
<para>RTSの時計が進行する時間間隔を設定する。これは時間・確保量プロファイルの標本間隔でもある。デフォルトは0.02秒</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-xc</option>
<indexterm><primary><option>-xc</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>このオプションは、例外が発生するたびにその時点のコスト集約点スタックをランタイムが表示するようにする。これは、悪名高い<literal>Prelude.head: empty list</literal>エラーなど、例外の位置をデバッグするのに特に便利である。<xref linkend="rts-options-debugging"/>を見よ。</para>
</listitem>
</varlistentry>
</variablelist>
</sect1>
<sect1 id="prof-heap">
<title>メモリ使用状況のプロファイルを取る</title>
<para>プログラムの時間的、及びメモリ確保の挙動についてプロファイルを取るのに加えて、プログラムのメモリ使用状況の時間的な変化を表したグラフを生成することができる。これは、プログラムが実行時に必要以上のメモリを使っているときに<firstterm>スペースリーク</firstterm>の原因を探るのに便利である。スペースリークはGCを酷使するので実行が遅くなりがちであるし、場合によってはプログラムがメモリを使い果たすこともあり得る。</para>
<para>プログラムのヒーププロファイルを生成するには、以下の手順に従う。</para>
<orderedlist>
<listitem>
<para>プログラムをプロファイル用にコンパイルする。(<xref linkend="prof-compiler-options"/>)</para>
</listitem>
<listitem>
<para>下で解説されているヒーププロファイルオプションのどれかを付けて実行する。(例えば、基本的な生産者プロファイルなら<option>-h</option>)。これで<filename><replaceable>prog</replaceable>.hp</filename>というファイルが生成される。</para>
</listitem>
<listitem>
<para><command>hp2ps</command>を実行して、Postscriptファイルである<filename><replaceable>prog</replaceable>.ps</filename>を作る。<command>hp2ps</command>ユーティリティは<xref linkend="hp2ps"/>で詳述されている。</para>
</listitem>
<listitem>
<para>できたヒーププロファイルを<application>Ghostview</application>のようなpostscript閲覧器を使って表示するか、Postscript対応のプリンタで印刷する。</para>
</listitem>
</orderedlist>
<para>例として、これは上の<xref linkend="scc-pragma" />で与えたプログラムに対して生成されたヒーププロファイルである。</para>
<!-- contentwidth/contentheight don't appear to have any effect
other than making the PS file generation work, rather than
falling over. The result seems to be broken PS on the page
with the image. -->
<imagedata fileref="prof_scc" contentwidth="645px"
contentdepth="428px"/>
<para>ヒーププロファイルを表示するためのより高度な道具の集まり(GHCには付属していない)として、<ulink url="http://www.haskell.org/haskellwiki/Hp2any">hp2any</ulink>も見てみると良いかもしれない。</para>
<sect2 id="rts-options-heap-prof">
<title>ヒーププロファイルのためのRTSオプション</title>
<para>生成できるヒーププロファイルの種類は複数ある。どの種類でも出力は時間に対する生存ヒープのグラフだが、生存ヒープを分類するときの内訳の取りかたが違う。分類の方法は以下のRTSオプションで選択する。</para>
<variablelist>
<varlistentry>
<term>
<option>-hc</option>
<indexterm><primary><option>-hc</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>(-hに短縮可能)そのデータを生成したコスト集約点スタックで分類する。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hm</option>
<indexterm><primary><option>-hm</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>そのデータを生成したコードの所属するモジュールで分類する。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hd</option>
<indexterm><primary><option>-hd</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para><firstterm>クロージャの説明</firstterm>で分類する。実際のデータについては、説明とは構築子の名前のことである。それ以外のクロージャについてはそれを識別するコンパイラ生成の文字列である。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hy</option>
<indexterm><primary><option>-hy</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para><firstterm>型</firstterm>で分類する。型が不明/多相的な関数については、実際の型を近似する文字列が使われる。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hr</option>
<indexterm><primary><option>-hr</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para><firstterm>維持原因(retainer)集合</firstterm>で分類する。維持原因プロファイルは下で詳しく解説されている。(<xref linkend="retainer-prof"/>)</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hb</option>
<indexterm><primary><option>-hb</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para><firstterm>経歴</firstterm>で分類する。経歴プロファイルは以下でより詳しく説明されている。(<xref linkend="biography-prof"/>)</para>
</listitem>
</varlistentry>
</variablelist>
<para>さらにプロファイルは特定の条件を満たすヒープデータに限って行うことができる。例えば、プロファイルを型ごとに表示したいが、これを特定のモジュールで生産されたデータについてのみ行いたい、あるいは、特定の型のデータについて維持原因のプロファイルを行いたい、ということがあるかもしれない。このような制限は以下のように指定できる。</para>
<variablelist>
<varlistentry>
<term>
<option>-hc</option><replaceable>name</replaceable>,...
<indexterm><primary><option>-hc</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>生産地点において「指定されたコスト集約点のいずれかがスタックの先頭にあった」クロージャのみを対象にする。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hC</option><replaceable>name</replaceable>,...
<indexterm><primary><option>-hC</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>生産地点において「指定されたコスト集約点のいずれかがスタックのどこかにあった」クロージャのみを対象にする。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hm</option><replaceable>module</replaceable>,...
<indexterm><primary><option>-hm</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>特定のモジュールで生産されたクロージャのみを対象にする。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hd</option><replaceable>desc</replaceable>,...
<indexterm><primary><option>-hd</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>説明文字列が指定されたものと一致するクロージャのみを対象にする。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hy</option><replaceable>type</replaceable>,...
<indexterm><primary><option>-hy</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>指定された型のクロージャのみを対象にする。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hr</option><replaceable>cc</replaceable>,...
<indexterm><primary><option>-hr</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>維持原因集合の中に「指定されたコスト集約点のいずれかを先頭とするスタックがある」クロージャのみを対象にする。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-hb</option><replaceable>bio</replaceable>,...
<indexterm><primary><option>-hb</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>指定された経歴のいずれかに該当するクロージャのみを対象にする。<replaceable>bio</replaceable>は<literal>lag</literal>、<literal>drag</literal>、<literal>void</literal>、<literal>use</literal>のいずれかである。</para>
</listitem>
</varlistentry>
</variablelist>
<para>例として、以下のオプションでは、構築子<literal>Branch</literal>及び<literal>Leaf</literal>に限定した保持原因プロファイルが生成される。</para>
<screen>
<replaceable>prog</replaceable> +RTS -hr -hdBranch,Leaf
</screen>
<para>「分類」オプション(上記の例では<option>-hr</option>)は一つしか与えられないが、適用できる制約の数に上限はない。全てのオプションは基本的に組み合わせ可能であるが、例外として、GHCは今のところ<option>-hr</option>と<option>-hb</option>オプションを併用するのをサポートしていない。</para>
<para>ヒーププロファイルに関するオプションがあと三つある。</para>
<variablelist>
<varlistentry>
<term>
<option>-i<replaceable>secs</replaceable></option>:
<indexterm><primary><option>-i</option></primary></indexterm>
</term>
<listitem>
<para>プロファイル間隔(標本取得間隔)を<replaceable>secs</replaceable>秒(デフォルトは0.1秒)に設定する。小数も使える。例えば<option>-i0.2</option>とすれば毎秒五回標本を取得する。これが影響するのはヒーププロファイルだけである。時間プロファイルでは常にRTS時計に合わせた周期で標本が取得される。これを変えることについては<xref linkend="prof-time-options"/>を見よ。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-xt</option>
<indexterm><primary><option>-xt</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>ヒーププロファイルに、スレッドが占めるメモリを含める。個々のスレッドは、スタック用の空間(スタックは通常小さい状態で開始し、必要に応じて成長する)と、それに加えてスレッド状態のための小さな領域を使う。</para>
<para>これには主スレッドも含まれるので、<option>-xt</option>はプログラムが使っているスタック空間の大きさを知るのに良い。</para>
<para>スレッドが占めるメモリとスタックは、クロージャの説明での分類、型での分類に際しそれぞれ「TSO」「STACK」と表示される。</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
<option>-L<replaceable>num</replaceable></option>
<indexterm><primary><option>-L</option></primary><secondary>RTS option</secondary></indexterm>
</term>
<listitem>
<para>ヒーププロファイルにおける、コスト集約点スタックの名前の長さの最大値を設定する。デフォルトは25。</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
<sect2 id="retainer-prof">
<title>維持原因プロファイル</title>
<para>維持原因プロファイルは<quote>なぜこのデータが回収されずに残っているのか</quote>という類の疑問に答えるように設計された。まず、維持原因オブジェクトとは何かを定義しよう。</para>
<blockquote>
<para>維持原因オブジェクトとは、システムスタックまたは未評価のクロージャ(サンク)または明示的に可変(mutable)なオブジェクトである。</para>
</blockquote>
<para>特に、構築子は維持原因オブジェクト<emphasis>ではない</emphasis>。</para>
<para>オブジェクトBがオブジェクトAの維持原因であるのは、(i)Bが維持原因オブジェクトであって、(ii)Bから始めてポインタを再帰的にたどることで、途中で他の維持原因オブジェクトに出会うことなくAに到達できる場合である。全ての生存オブジェクトは一つ以上の維持原因によって維持されているが、これらの維持原因を総称して、そのオブジェクトの維持原因集合、または<firstterm>維持原因集合</firstterm>、または<firstterm>維持原因たち</firstterm>と呼ぶ。</para>
<para>プログラムに<option>-hr</option>オプションが与えられ、維持原因プロファイルが要請されると、維持原因集合で分類されたグラフが生成される。維持原因集合はコスト集約点スタックの集合として表示される。通常これはプロファイルグラフに載せるには大きすぎるので、維持原因集合には一つずつ番号が振られ、グラフ中では番号つきで短縮して表示される。そして、維持原因集合の完全な一覧は<filename><replaceable>prog</replaceable>.prof</filename>というファイルに出力される。</para>
<para>維持原因プロファイルでは、全てのオブジェクトについて維持原因集合を得るために生存ヒープを複数回走査しなければならず、これは場合によってはとても遅い。このため、維持原因集合の最大サイズが設定されていて、これよりも大きな維持原因集合は<literal>MANY</literal>という特殊な集合になる。この最大サイズはデフォルトでは8であり、RTSオプション<option>-R</option>で変更できる。</para>
<variablelist>
<varlistentry>
<term><option>-R</option><replaceable>size</replaceable></term>
<listitem>
<para>維持原因集合の要素数を<replaceable>size</replaceable>に制限する。(デフォルト: 8)</para>
</listitem>
</varlistentry>
</variablelist>
<sect3>
<title>維持原因プロファイルに関するヒント</title>
<para>維持原因オブジェクトの定義は、スペースリークの良くある原因を反映するようにしてある。つまり、大きな構造がある未評価の計算によって保持され、その計算が実行され次第開放される、という状況である。一つの好例として、有限写像から値を見つけ出す(lookup)操作がある。このlookup操作がタイミング良く実行されないと、未評価のlookup操作が写像全体を生き長らえさせることになる。この種のスペースリークは、<literal>seq</literal>やデータ構築子のフィールドの正確性注釈を使って、関連する計算を強制することで防げることが多い。</para>
<para>特定のデータ構造が一連の未評価のクロージャの列によって保持されているということが良くある。この場合、最も近いものだけが維持原因プロファイルで報告される。例えば、AがBを保持し、BがCを保持し、Cが大きな構造を保持しているとしよう。さらに、Bはたくさんあるが、Aは一つしかなく、従ってAを排除したいということがあるかもしれない。しかし、保持原因プロファイルはこの場合大きな構造の保持原因としてBを挙げる。そこで、この列を一つたどるために、Bのオブジェクトに絞ってもう一回維持原因プロファイルを取ることができる。こうすることで、Bの維持原因プロファイルが得られる。</para>
<screen>
<replaceable>prog</replaceable> +RTS -hr -hcB
</screen>
<para>この技は完璧ではない。無関係なBのクロージャがヒープ中にあるかもしれないからである。しかし、これで大抵の場合うまく行くということが判っている。</para>
</sect3>
</sect2>
<sect2 id="biography-prof">
<title>経歴プロファイル</title>
<para>典型的なヒープオブジェクトは、生存期間中の各時点において以下のいずれかの状態をとる。</para>
<itemizedlist>
<listitem>
<para><firstterm>lag(待機)</firstterm>段階。作られてから最初に使われるまでの間。</para>
</listitem>
<listitem>
<para><firstterm>use(使用中)</firstterm>段階。最初に使われてから最後に使われるまでの間。</para>
</listitem>
<listitem>
<para><firstterm>drag(惰性)</firstterm>段階。最後に使われてから参照されなくなるまでの間。</para>
</listitem>
<listitem>
<para>一回も使われないオブジェクトについては、生存期間中常に<firstterm>void(空虚)</firstterm>状態にあるとみなされる。</para>
</listitem>
</itemizedlist>
<para>経歴ヒーププロファイルでは、上記の四状態にある生存ヒープの割合を表示する。通常、最も重要なのはvoid状態とdrag状態である。これらの状態にある生存ヒープはlag状態やuse状態にあるヒープよりも無駄である可能性が高い。</para>
<para>これらのうち一つまたは複数の状態にあるものについて、別の基準で分類することもできる。これには、プロファイルを経歴で制限すれば良い。例えば、drag状態またはvoid状態にあるヒープについて、割合を生産者別で表示するなら、以下のようにすれば良い。</para>
<screen>
<replaceable>prog</replaceable> +RTS -hc -hbdrag,void
</screen>
<para>drag状態やvoid状態にあるヒープの生産者や型が分かったら、次にすることは維持原因を見つけることだろう。</para>
<screen>
<replaceable>prog</replaceable> +RTS -hr -hc<replaceable>cc</replaceable>...
</screen>
<para>注意: このように二段階に分けて処理しないといけないのは、現在GHCがプロファイルを取るときに経歴と維持原因の両方を同時に使うことができないからである。</para>
</sect2>
<sect2 id="mem-residency">
<title>実際のメモリ使用量</title>
<para>ヒーププロファイルで報告されるメモリ使用量と、プログラムを実行したときの実際のメモリ使用量とはどう関係しているか。ヒーププロファイルで報告されるメモリ使用量と、システムのツール(Unixなら<literal>ps</literal>や<literal>top</literal>、Windowsならタスクマネージャなど)で報告されるメモリ使用量との間に大きな差があるのを目にするかも知れない。これにはいくつかの原因がある。</para>
<itemizedlist>
<listitem>
<para>プロファイル自体にオーバーヘッドがあるが、プロファイル時のメモリ使用量の数値からは引かれている。もちろん、このオーバーヘッドはプロファイルのサポートなしでコンパイルすれば消滅する。現在、空間オーバーヘッドはヒープオブジェクト一つあたり二ワードであり、この結果30%程度のオーバーヘッドになる。</para>
</listitem>
<listitem>
<para>GCには実際の使用量よりもたくさんのメモリが必要である。この比は使われているGCのアルゴリズムに依存する。標準である世代別コピーGCでは、Lを生存データの量として通常3Lバイトのメモリを必要とする。これは、デフォルトでは(<option>+RTS -F</option>オプションを見よ)古い世代が回収前の時点で二倍(2L)になり得、さらにコピー先としてLバイトの空間が必要だからである。コンパクト化GC(<option>+RTS -c</option>オプションを見よ)を使うときは、これは2Lに減り、<option>-F</option>を調整することでさらに減らせる。また、確保領域の大きさも加わる。(現在は512kbに固定されている)</para>
</listitem>
<listitem>
<para>デフォルトではヒーププロファイルにはスタックが含まれない。<option>+RTS -xt</option>オプションを見よ。</para>
</listitem>
<listitem>
<para>プログラムテキスト自体、Cスタック、あらゆるヒープ外データ(例えば、外部のライブラリで確保されたデータやRTSが確保したデータ)、<literal>mmap()</literal>されたメモリはヒーププロファイルで考慮されない。</para>
</listitem>
</itemizedlist>
</sect2>
</sect1>
<sect1 id="hp2ps">
<title><command>hp2ps</command>--ヒーププロファイルをPostScriptへ</title>
<indexterm><primary><command>hp2ps</command></primary></indexterm>
<indexterm><primary>heap profiles</primary></indexterm>
<indexterm><primary>postscript, from heap profiles</primary></indexterm>
<indexterm><primary><option>-h<break-down></option></primary></indexterm>
<para>使いかた:</para>
<screen>
hp2ps [flags] [<file>[.hp]]
</screen>
<para><command>hp2ps</command><indexterm><primary>hp2ps program</primary></indexterm>は、RTSオプション<option>-h<brak-down></option>を使って作られたヒーププロファイルをPostScriptのグラフに変換するプログラムである。<command>hp2ps</command>が処理するファイルの拡張子は慣習に従って<filename>.hp</filename>とされる。PostScript出力は<filename><file>@.ps</filename>に書き込まれる。<filename><file></filename>が完全に省かれた場合は、このプログラムはフィルタとして動作する。</para>
<para><command>hp2ps</command>はGHCソース配布物中の<filename>ghc/utils/hp2ps</filename>にある。これはもともとHBC/LMLヒーププロファイラの一部としてDave Wakelingが開発したものである。</para>
<para>フラグは以下のものがある。</para>
<variablelist>
<varlistentry>
<term><option>-d</option></term>
<listitem>
<para>グラフを見やすくするために、<command>hp2ps</command>は項目の帯をソートする。デフォルトのソート順では、面積の大きい帯が小さい帯の上に置かれる。<option>-d</option>を使うと、よりギザギザの(元の値の標準偏差が大きい)帯が上に置かれるようになる。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-b</option></term>
<listitem>
<para>通常、<command>hp2ps</command>はグラフの表題をページの上部の小さいボックスの中に配置する。しかし、JOB文字列が長すぎて小さいボックスに収まらないとき(35文字より長いとき)は、<command>hp2ps</command>は代わりに大きいボックスを使う。<option>-b</option>は、<command>hp2ps</command>が大きいボックスを使うように強制する。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-e<float>[in|mm|pt]</option></term>
<listitem>
<para>LaTex文書に含めるのに適したencapsulated PostScriptを生成する。通常、PostScriptのグラフは高さ6インチ幅9インチのランドスケープモードで描画され、<command>hp2ps</command>はこの領域をa4用紙のほぼ中心に配置する。これはグラフを精査するには良いが、LaTex文書に取り込むのには不向きである。<option>-e</option>オプションが指定されるとグラフはポートレートモードで描画され、幅は<float>で指定される。単位はインチ、ミリメートル、ポイント(デフォルト)のいずれかである。結果としてできるPostScriptファイルはEncapsulated PostScript(EPS)としての基準を満たしており、Rokickiのdvi-PostScript変換器である<command>dvips</command>を使ってLaTex文書に含めることができる。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-g</option></term>
<listitem>
<para>PostScriptプレビューワ<command>gs</command>(および類似品)に適した出力を作る。この場合グラフはスケーリングなしのポートレートモードで印刷される。この出力はレーザプリンタには向かない。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-l</option></term>
<listitem>
<para>通常プロファイル中の帯の数は20に制限され、それ以外の識別子は全て<literal>OTHER</literal>という帯にまとめられる。<option>-l</option>フラグを使うとこの20個の制限は取り除かれ、必要なだけ帯を作るようにする。項目一覧はどうせ収まりきらないので生成されない。たくさんの帯のある時間プロファイルを作るのに便利である。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-m<int></option></term>
<listitem>
<para>通常プロファイル中の帯の数は20に制限され、それ以外の識別子は全て<literal>OTHER</literal>という帯にまとめられる。<option>-m</option>フラグを使うとこの個数制限を変更することができる。(最大20)</para>
<para><option>-m0</option>とすると帯の数は無制限になり、必要なだけ帯を作るようにする。項目一覧はどうせ収まりきらないので生成されない。たくさんの帯のある時間プロファイルを作るのに便利である。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-p</option></term>
<listitem>
<para>前回のパラメタを使う。デフォルトではPostScriptグラフはページ全体を占めるように自動的に上下左右に伸縮される。しかし、一連のグラフを提示したいときには、新しいグラフを描くときに前回と同じ縮尺、色づけ、順序を使えると便利である。<option>-p</option>フラグは前回<command>hp2ps</command>を<filename>file</filename>に対して使ったときのパラメタを使ってグラフを描くようにする。これらのパラメタは<filename>file@.aux</filename>から得られる。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-s</option></term>
<listitem>
<para>表題用に小さいボックスを使う。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-t<float></option></term>
<listitem>
<para>通常、合計してプロファイルの1%に満たないトレース要素はプロファイルに含められない。<option>-t</option>オプションを使うとこのパーセント値を変更することができる。(最大5%)</para>
<para><option>-t0</option>とすると、全てのトレース要素がプロファイルに含められる。この結果、全てのデータが表示されることが確かになる。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-c</option></term>
<listitem>
<para>色付きの出力を生成する。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-y</option></term>
<listitem>
<para>マークを無視する。</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>-?</option></term>
<listitem>
<para>使いかたに関する情報を表示する。</para>
</listitem>
</varlistentry>
</variablelist>
<sect2 id="manipulating-hp">
<title>hpファイルを操作する</title>
<para>(Jan-Willem Maessenが提供してくれた覚え書き)</para>
<para>プログラム<filename>FOO</filename>についてヒーププロファイルを行うと<filename>FOO.hp</filename>が生成されるが、これはとても単純な構造のテキストファイルである。以下に示すのは代表例だが、実際のデータは大部分省略されている。
<screen>
JOB "FOO -hC"
DATE "Thu Dec 26 18:17 2002"
SAMPLE_UNIT "seconds"
VALUE_UNIT "bytes"
BEGIN_SAMPLE 0.00
END_SAMPLE 0.00
BEGIN_SAMPLE 15.07
... sample data ...
END_SAMPLE 15.07
BEGIN_SAMPLE 30.23
... sample data ...
END_SAMPLE 30.23
... etc.
BEGIN_SAMPLE 11695.47
END_SAMPLE 11695.47
</screen>
最初の四行(<literal>JOB</literal>、<literal>DATE</literal>、<literal>SAMPLE_UNIT</literal>、<literal>VALUE_UNIT</literal>)はヘッダである。<literal>BEGIN_SAMPLE</literal>ではじまり<literal>END_SAMPLE</literal>で終わる行の塊が一つの標本(sample)である。(ヒーププロファイルのグラフを縦方向に切った断面だと思うと良い)。正しい書式のヘッダのあとに*完全な*標本がいくつか続くという形式であれば、hp2psはどんなファイルでも受け付けるはずである。 </para>
</sect2>
<sect2>
<title>プロファイルの特定の部分に注目する</title>
<para>プロファイルの特定の部分だけを見るには、<filename>.hp</filename>ファイルのコピーをテキストエディタで編集して不要な標本を削除してしまうだけで良い。できた<filename>.hp</filename>ファイルは<command>hp2ps</command>に通せるので、後は閲覧したり印刷したりできる。</para>
</sect2>
<sect2>
<title>実行中のプログラムのヒーププロファイルを見る</title>
<para><filename>.hp</filename>ファイルはプログラムの実行中に徐々に生成される。原理的には、この不完全なファイルに対して<command>hp2ps</command>を実行することでプログラムのヒープ使用の現況報告を生成することができる。しかし、このファイルの最後の標本が不完全かも知れず、その場合には<command>hp2ps</command>が失敗する。UNIXユーティリティのある機械を使っているならこの問題を回避するのはそんなに難しくない。(コマンドがちょっと複雑怪奇ではあるが)
<screen>
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
| hp2ps > FOO.ps
</screen>
<command>fgrep -n END_SAMPLE FOO.hp</command>というコマンドは<filename>FOO.hp</filename>中で完全な標本が終わるところを全て列挙し、行番号を付加する。これを元に、<command>tail</command>と<command>cut</command>を使って最後の完全な標本の行番号を取得できる。これが<command>head</command>への引数として使わる。こうすることで、<filename>FOO.hp</filename>から末尾の不完全な標本を削除したのと同様の結果になる。できたものは正しい書式の.hpファイルであり、<command>hp2ps</command>に直接入力できる。</para>
</sect2>
<sect2>
<title>ヒーププロファイルを実時間で閲覧する</title>
<para>
<command>gv</command>や<command>ghostview</command>といったプログラムには「ファイルを監視する」というオプションがあり、これを使ってプログラムの実行にあわせてその時点のヒーププロファイルを見ることができる。これには、徐々に伸長するプロファイルを前の節で解説した方法で作り、それに対して<command>gv</command>を次のように実行すれば良い。
<screen>
gv -watch -seascape FOO.ps
</screen>
<literal>-watch</literal>フラグを付け忘れた場合でも「State」メニューから「Watch File」を選べば良い。これで、<filename>FOO.ps</filename>が新しくなるごとに表示が自動的に更新される。</para>
<para>
これを全部一つのスクリプトにまとめることができる。
<screen>
#!/bin/sh
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
| hp2ps > FOO.ps
gv -watch -seascape FOO.ps &
while [ 1 ] ; do
sleep 10 # We generate a new profile every 10 seconds.
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
| hp2ps > FOO.ps
done
</screen>
不完全な状態の<filename>FOO.ps</filename>を読み込もうとして<command>gv</command>がフリーズすることがある。(これは、更新時で<command>hp2ps</command>が走っている最中だったからである)。スクリプトはもう少し複雑になるが、gvがSIGHUPを受け取ると入力ファイルを再読み込みするという事実を使うと、次のようにしてこの問題を回避できる。
<screen>
#!/bin/sh
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
| hp2ps > FOO.ps
gv FOO.ps &
gvpsnum=$!
while [ 1 ] ; do
sleep 10
head -`fgrep -n END_SAMPLE FOO.hp | tail -1 | cut -d : -f 1` FOO.hp \
| hp2ps > FOO.ps
kill -HUP $gvpsnum
done
</screen>
</para>
</sect2>
</sect1>
<sect1 id="prof-threaded">
<title>並列・並行プログラムのプロファイルを取る</title>
<para><option>-threaded</option>と<option>-prof</option>の組み合わせは全く問題なく、実際<option>+RTS -N</option>オプションを使って複数のプロセッサ上で走っているプログラムのプロファイルを取ることは可能である。<footnote>この機能はGHC 7.4.1で追加された。</footnote>
</para>
<para>ただしいくつか注意点がある。現在の実装では、ロックを要する共有データ構造をプロファイル実装にいくつか使っているため、プロファイル版プログラムは通常よりずっとスケールしない可能性が高い。さらに、プロファイル版プログラムが収集するメモリ確保量の統計は、共有メモリに置かれるものの(速度のために)ロック<emphasis>されない</emphasis>。したがってこれらの数字は並列プログラムについては不正確なものになる。
</para>
<para>複数コア上でプロファイルを取るためにプログラムをコンパイルするときは、<option>-fno-prof-count-entries</option>を使うことを強く推奨する。これは、進入回数もまた共有メモリにあり、複数のコアでこれを常に更新し続けるのは極めて遅いためである。
</para>
<para>また、並列プログラムのプロファイルを取るには<ulink url="http://www.haskell.org/haskellwiki/ThreadScope">ThreadScope</ulink>を使うことを推奨する。これは並列実行を視覚化するGUIを提供し、GHCが提供する時間と空間プロファイルを補完する。
</para>
</sect1>
<sect1 id="hpc">
<title>コード網羅率を観察する</title>
<indexterm><primary>code coverage</primary></indexterm>
<indexterm><primary>Haskell Program Coverage</primary></indexterm>
<indexterm><primary>hpc</primary></indexterm>
<para>
コード網羅ツールは、コードのどの部分が実際に実行され、どの部分が一回も呼ばれなかったかを、プログラマが判断できるようにする。GHCには計器付きコードを生成するオプションがあり、生成されたコードはHaskell Program Coverage(HPC)ツールキット(GHCに付属している)の一員としてコード網羅率を記録する。このコード網羅率情報は、HPCツールを使って人間が理解できる形式に変換することができる。</para>
<para>正しい計器付きコードは二種類の網羅率情報を供給する。ソース網羅率と真偽値制御網羅率である。ソース網羅率は、プログラムがどれくらい隅々まで使われたかの度合いで、三つの水準、すなわち宣言(最上位のものと局所的なものの両方)、分岐(複数の等式やcaseの枝からの選択)、式(あらゆる深さのもの)で測られる。真偽値網羅率は、構文的に真偽値が要求される全ての場所(ガード、条件、qualifier(訳者: qualifierって何?))について、どれくらいTrueとFalseの両方が得られたかである。</para>
<para>HPCは、この両方の種類の情報を、二つの主要な方法で表示する。一つは、統計要約の付いたテキスト形式の報告書(<literal>hpc report</literal>)であり、もう一つはソースの色付きマークアップ(<literal>hpc markup</literal>)である。真偽値網羅については、それぞれのガード、条件、qualifierについて、四つの結果があり得る。TrueとFalseの両方が起こった、Trueだけ、Falseだけ、一回も評価されなかった、である。hpcマークアップの出力において、黄色の背景による強調はプログラムのその部分が一回も評価されなかったことを示し、緑の背景は常にTrueだったことを、赤い背景は常にFalseだったことを示す。</para>
<sect2><title>小さな例: 逆数をとる</title>
<para>
例として、<filename>Recip.hs</filename>という、逆数の正確な十進表現を計算するプログラムを使おう。小数の循環部分は括弧に入れて示すことにする。
</para>
<programlisting>
reciprocal :: Int -> (String, Int)
reciprocal n | n > 1 = ('0' : '.' : digits, recur)
| otherwise = error
"attempting to compute reciprocal of number <= 1"
where
(digits, recur) = divide n 1 []
divide :: Int -> Int -> [Int] -> (String, Int)
divide n c cs | c `elem` cs = ([], position c cs)
| r == 0 = (show q, 0)
| r /= 0 = (show q ++ digits, recur)
where
(q, r) = (c*10) `quotRem` n
(digits, recur) = divide n r (c:cs)
position :: Int -> [Int] -> Int
position n (x:xs) | n==x = 1
| otherwise = 1 + position n xs
showRecip :: Int -> String
showRecip n =
"1/" ++ show n ++ " = " ++
if r==0 then d else take p d ++ "(" ++ drop p d ++ ")"
where
p = length d - r
(d, r) = reciprocal n
main = do
number <- readLn
putStrLn (showRecip number)
main
</programlisting>
<para>HPCの計器付与は-fhpcフラグで有効になる。</para>
<screen>
$ ghc -fhpc Recip.hs
</screen>
<para>GHCはカレントディレクトリに<filename>.hpc</filename>サブディレクトリを作成し、HPCインデックス(<filename>.mix</filename>)ファイルを、コンパイルするモジュール一つにつき一個そこに置く。これらのファイルについて心配する必要はない。これらは、プログラムが実行された後に<literal>hpc</literal>ツールがコンパイルされたモジュールの網羅率データを生成するのに必要な情報を含んでいる。</para>
<screen>
$ ./Recip
1/3
= 0.(3)
</screen>
<para>プログラムを実行すると接尾辞<literal>.tix</literal>を持つファイル、この例では<filename>Recip.tix</filename>が生成される。これはプログラムの今回の実行の網羅率データを含んでいる。プログラムは(たとえば違うテストデータで)複数回走らせることができ、そうすると別々の実行の網羅率データがこの<filename>.tix</filename>に蓄積される。網羅率データをリセットしてやりなおすには、単に<filename>.tix</filename>ファイルを削除すれば良い。
</para>
<para>プログラムを走らせたので、テキスト形式で網羅率の要約を生成することができる。</para>
<screen>
$ hpc report Recip
80% expressions used (81/101)