4.17. コンパイル済みプログラムを実行する

実行可能なプログラムを作るとき、GHCシステムはコードをコンパイルし、それを自明でないランタイムシステム(RTS)とリンクする。これは、記憶領域の管理、スレッドのスケジュール、プロファイルの取得などを行う。

RTSは、振る舞いを制御するための沢山のオプションを持っている。例えば、コンテキストスイッチの間隔やヒープのデフォルトの大きさを変えたり、ヒーププロファイルを有効にしたりである。こうしたオプションは色々な方法でランタイムシステムに渡すことができる。次の節(4.17.1. RTSオプションを設定する)ではこれらの方法を記述し、それ以降の節ではRTSオプション自体について記述する。

4.17.1. RTSオプションを設定する

RTSオプションを設定するのには四つの方法がある。

4.17.1.1. コマンド行でRTSオプションを設定する

リンク時に-rtsoptsフラグを適切に設定していたなら、プログラムを実行するときにRTSオプションをコマンド行で与えることができる。

Haskellプログラムの実行が開始されるとき、RTSはコマンド行引数から+RTS-RTSで囲まれた部分を自分用に抽出する。以下のような例を考える。

$ ghc prog.hs -rtsopts
[1 of 1] Compiling Main             ( prog.hs, prog.o )
Linking prog ...
$ ./prog -f +RTS -H32m -S -RTS -h foo bar

RTSは-H32m -Sを横取りし、プログラムがSystem.Environment.getArgsを読んだときには残りの引数である-f -h foo barが渡される。

次の例のように、ランタイムシステムへのオプションがコマンド行の最後まで続くときは、-RTSオプションは必要ない。

% hls -ltr /usr/etc +RTS -A5m

残りのオプションを問答無用で(RTSでなく)プログラムに渡したいなら、--RTSを使うことができる。

いつもと同様、sizeを取るRTSオプションについては、以下が適用される。sizeの最後の文字がKまたはkなら、数値は1000倍される。Mまたはmなら、1,000,000倍される。GまたはGなら、1,000,000,000倍される。(カウンタのオーバーフローはあなたの責任である)

+RTS -?オプションを与えれば、実際に利用可能なRTSオプションを表示することができる。(これはどのようにコンパイルされたかによって異なる)

注意: GHCはそれ自身GHCでコンパイルされているので、通常の+RTS ... -RTSの組み合わせを使ってRTSオプションを変更することができる。例えば、コンパイル時の最大ヒープサイズを128Mに増やすには、+RTS -M128m -RTSをコマンド行に加えれば良い。

4.17.1.2. コンパイル時にRTSオプションを設定する

-with-rtsoptsフラグ(4.12.6. リンクに影響するオプション)を使うことで、プログラムのRTSオプションをコンパイル時に設定することができる。これのよくある使い方は、デフォルトよりも大きなヒープ/スタックサイズを初期値としてプログラムに与えることである。例えば-H128m -K64mを設定するには、-with-rtsopts="-H128m -K64m"付きでリンクすればよい。

4.17.1.3. RTSOPTSでRTSオプションを設定する

リンク時に-rtsoptsフラグがnone以外に設定されているなら、RTSオプションは環境変数GHCRTSからも採られる。例えば、GHCでコンパイルされた全てのプログラムについて最大ヒープサイズを2Gにするなら、次のようにすれば良い。(sh系のシェルを使っていると仮定する)

GHCRTS='-M2G'
export GHCRTS

GHCRTS環境変数から採られたRTSオプションはコマンド行からオプションを与えることで上書きできる。

ヒント: あなたの環境にGHCRTS=-M2Gのようなものを設定しておくと、Haskellプログラムがあなたのマシンの実メモリを超えて成長することを簡単に防ぐことができる。誤ってこういうことをするのは良くあることであり、OSがそのプロセスを殺すことを決めるまでマシンがゆっくりと地を這うことになる。(そして、ちゃんと正しいのを殺してくれるといいね)

4.17.1.4. RTSの振る舞いを変更するためのフック

GHCでは、ランタイムシステムから呼ばれるフックをコンパイル時に含めることで、任意のプログラムのRTS設定の一部をある程度制御することができる。RTSにはこれらのフックのスタブ定義が含まれているが、自分の版を書いてGHCのコマンド行からリンクすることで、デフォルトと異なるものを使える。

DLLリンクの気まぐれのせいで、これらのフックはWindowsでプログラムが動的リンクでビルドされるときには働かない。

ランタイムシステムがどうしようもなくなったとき(例えばスタックオーバーフロー)に表示される文言を変更することができる。このためのフックは以下である。

void OutOfHeapHook (unsigned long, unsigned long)

ヒープオーバーフローのメッセージ。

void StackOverflowHook (long int)

スタックオーバーフローのメッセージ。

void MallocFailHook (long int)

mallocが失敗したときに表示されるメッセージ。

4.17.2. いろいろなRTSオプション

-Vsecs

RTSの時計が進行する時間間隔を設定する。ランタイムは単一のタイマシグナルを使って時計を進行させる。コンテクストスイッチのタイマ(4.14. Concurrent Haskellを使う)およびヒーププロファイル時のタイマ(5.4.1. ヒーププロファイルのためのRTSオプション)を制御するのにはこれが使われる。また、時間プロファイルをとるときは、プロファイルの標本を記録するのにRTSのタイマシグナルを直接使うことになる。

通常、-Vオプションを直接指定する必要はない。-C-iオプションで短い間隔が指定されたときは、自動的にRTSタイマの分解能も調整されるからである。しかし、時間プロファイルの分解能を増したいときには、-Vを設定することが必要になる。

値0を使うと、RTSの時計は完全に無効にされ、それに依存したタイマも無効になる。コンテクストスイッチのタイマとプロファイルタイマである。それでもコンテクストスイッチは発生するが、決定論的に、かつ通常よりずっと高い頻度で発生するようになる。インターバルタイマを無効にすると、実行時の非決定性の源が一つ除かれるわけであり、デバッグに便利である。

--install-signal-handlers=yes|no

yes(デフォルト)なら、RTSはctrl-Cの類を捕捉するためのシグナルハンドラを設置する。このオプションは、HaskellコードをDLLとして使っていて、自分でシグナルハンドラを設定したいときに特に有用である。

--install-signal-handlers=noの場合でも、RTSの時間間隔タイマは変わらずに有効だということに注意。タイマシグナルはSIGVTALRMかSIGALRMで、RTSの設定とOSの能力によって異なる。このタイマシグナルを無効化するにはRTSオプション-V0を使う。(上記参照)

-xmaddress

警告: このオプションはメモリ確保の問題を回避するためだけのものである。GHCiが「failed to mmap() memory below 2Gb」のようなメッセージを表示して失敗するのでない限り、使ってはいけない。あなたの機械でGHCiを動かすのにこのオプションを使う必要があるなら、バグとして報告してほしい。

64ビットの機械では、RTSはアドレス空間の下方2Gbの中にメモリを確保する必要がある。種々のOSにおけるこれの対応は不完全であり、ときどき失敗する。このオプションは、アドレス空間の下方2Gbの中でどこのメモリを確保できるかについてRTSにヒントを与えるために存在する。例えば、+RTS -xm20000000 -RTSとすれば、RTSが0.5Gb markから確保を開始すべきというヒントになる。デフォルトは、可能なら下方2Gb中にメモリを確保するためのOS組み込みの手段(例えばLinuxならMAP_32BIT付きのmmap)を使い、それがなければ-xm40000000である。

4.17.3. ガベッジコレクタを制御するためのRTSオプション

ガベッジコレクタの挙動を精密に制御するために、いくつかのオプションが存在する。通常時には、これらはどれも使う必要がない(といいのだが)。それでも、最高の性能を得たいときのために、いくつかの箇所を調整することができるようになっている。

-A size

[デフォルト: 512k]ガベッジコレクタが使う確保領域の大きさを設定する。確保領域(実際には世代0・段階0)は固定されており、大きさが変わることはない。(-Hを使った場合はこの限りでない。下記参照)

確保領域の大きさを増すことは、性能の向上につながるかもしれないし、そうでないかもしれない。(より大きい確保領域を使うと、キャッシュの振舞は悪化するが、ガベッジコレクションの回数は減り、昇格(promotion)も少なくなる)

世代数が一しかないとき(-G1)は、-Aオプションは最小の確保領域を指定する。これは、実際の確保領域の大きさがヒープ中のデータの量によって変動するからである。(下記の-Fを見よ)

-c

もっとも古い世代を回収するときにコンパクト化アルゴリズムを使う。デフォルトでは最古世代はコピーアルゴリズムで回収されるが、このオプションが使われると、その場でコンパクト化されるようになる。コンパクト化アルゴリズムはコピーアルゴリズムより遅いが、相当量のメモリを節約できることがある。

ヒープの大きさ(これは-Hオプションで変えられる)が一定のとき、コンパクト化を使うことでかえってGCのコストが削減できるということがあり得る(GCの回数が減らせるかもしれないので)。ヒープの大きさに対する生存データの比が高いとき(>30%、例えば)には特にその傾向が強い。

注意: 現在、コンパクト化は、-G1で単一世代が指定されているときには動作しない。

-c n

[デフォルト: 30] 生存データがヒープの大きさの上限(-Mオプションを見よ)のn%を超えたときに自動的にコンパクト化を有効にする。デフォルトではヒープの大きさに上限はないので、-Msizeでヒープの大きさの上限が指定されない限り、このオプションには効果がない。

-F factor

[デフォルト: 2] このオプションは、古い世代のために予約されるメモリの量(two-space GCのときは確保領域の大きさ)を、生存データの量との比で指定する。例えば、最後に最も古い世代が回収されたときに生存データが2Mあったとすると、デフォルトでは、4Mにまでなるのを待ってから再び回収する。

これについてはデフォルトがうまく働いているようである。潤沢なメモリがあるなら、通常、-Ffactorを増やすよりも-Hsizeを使った方が良い。

-Fの値は、ヒープの最大の大きさ(-Msizeで設定する)に近づいたときには、ガベッジコレクタによって自動的に減らされる。

-G generations

[デフォルト: 2] ガベッジコレクタが使う世代の数を設定する。デフォルトの2は良い選択のようだが、ガベッジコレクタは任意の数の世代をサポートしている。プログラムが長い時間に渡って実行されるのでない限り、4程度より大きな値はおそらく良くない。最古の世代が一回も回収されないということになるからである。

+RTS -G1で一世代を指定すると、当然、単純な2-spaceコレクタになる。2-spaceコレクタでは、-Aオプション(上記参照)が指定するのは確保領域の最小の大きさである。これは、ヒープ中の生存データの量が増えるに従って確保領域も拡大されるからである。複数世代のコレクタでは確保領域の大きさは固定である。(-Hオプションを使う場合はこの限りでない。下を見よ)

-qg[gen]

[GHC 6.12.1から] [デフォルト: 0] 世代genとそれより上の世代において、並列GCを使う。genを省略すると、並列GCは完全に無効になり、逐次GCに戻る。

デフォルトの並列GC設定は、通常、並列プログラム(parやStrategyや複数のスレッドを使うもの)に適している。しかし、単一スレッドの逐次的なプログラムに関しても、並列GCを有効にする恩恵があることがある。特に、プログラムが大量のヒープデータを持ち、実行時間のかなりの部分をGCが占めるような場合である。逐次的なプログラムにおいて並列GCを使うには、適切な-Nオプションを使って並列ランタイムを有効にすればよい。加えて、-qg1を使って並列GCを古い世代のみに制限すると良いかもしれない。

-qb[gen]

[GHC 6.12.1から] [デフォルト: 1] 並列GCにおいて、世代genとそれより上の世代で負荷分散を使う。genを省略すると、負荷分散を完全に無効にする。

負荷分散とは、GCの作業を空いているコア間で分配することである。これは、ヒープが大きく、GCの作業を並列化する必要がある場合には良いことである。しかし、並列プログラムの若い世代における短い回収では、これは最悪である。なぜなら、データを、それが使われているCPUのキャッシュから、別のCPUのキャッシュへと動かすことで、局所性を損うからである。実際、並列プログラムでは、-qbで負荷分散を完全に無効にした方が良いこともある。

-H[size]

[デフォルト: 0] このオプションはガベッジコレクタに「推奨されるヒープの大きさ」を提示する。-Hsizeは可変の-Aオプションだと思うと良い。これは次のように述べる。私は少なくともsizeバイトを使いたいので、残ったものは全部-Aを増やすのに使え。

このオプションはヒープの大きさに制限を加えるものではない。ヒープは通常同様に与えられた大きさを超えて成長し得る。

sizeを省略すると、ガベッジコレクタは前回のGC時におけるヒープの大きさをsizeとして取る。これは、プログラムの全体的なメモリ要求を増やさずに、より大きな-Aの値を許す効果がある。長生きするデータを大量に作成するプログラムなど、デフォルトの小さな-Aの値が適していないときに使える。

-I seconds

(デフォルト: 0.3) スレッド化された、またはSMP版のRTS(-threadedを見よ。4.12.6. リンクに影響するオプション)においては、ランタイムが一定期間待機状態である(Haskellの計算が実行されていない)とき、自動的に大GCが実行される。GCが実行されるまでの時間を指定するのが-Isecondsオプションである。-I0を指定すると待機状態でのGCが無効になる。

対話的アプリケーションでは、待機時GCが多くの場合有効である。これは、Haskellの計算が起こっていない待機状態の間に、終了子を走らせたりデッドロックしたスレッドを感知したりできるからである。さらに、アプリケーションが忙しいときにGCが起こる可能性が減るので、反応性が向上するかもしれない。しかし、ヒープ中の生存データの割合があまりに高いと、待機時GCが重大な遅れを引き起こすことがある。また、時間間隔が短すぎると、対話的な反応性に悪影響を及ぼすことになるかもしれない。

これは実験的な機能である。問題が起きたり、改善案があるなら、知らせてほしい。

-ki size

[デフォルト: 1k] スレッドの初期スタックサイズを設定する。(注意: このフラグは単に-kであったが、GHC 7.2.1で-kiに改名された。古い名前も後方互換性のためにまだ受け付けられるが、将来の版では削除されるかもしれない)

スレッドのスタック(主スレッドのスタックも含めて)はヒープ上にとられる。スタックが成長するにつれ、必要に応じて新しいチャンクが追加される。スタックが再び収縮する場合、これらの追加されたスタックチャンクはGCによって回収される。デフォルトの初期スタックサイズは意図的に小さくしてある。これは、スレッド生成の時間的空間的オーバーヘッドを最小にとどめることで、ごく小さい仕事のためにスレッドを生成することを現実的にするためである。

-kc size

[デフォルト: 32k] “スタックチャンク”の大きさを設定する。スレッドの現行スタックがオーバーフローしたとき、-Kで設定された上限に達するまでは、新しいスタックチャンクが作成されてスレッドのスタックに加えられる。

チャンクサイズを小さくすることの利点はこうである。ガベッジコレクタは、チャンクが前回のGC以降変更されていないことが分かると、それらを走査しないで済ませることができる。よって、チャンクサイズを減らすと、ガベッジコレクタがより多くのスタックを未変更と知ることができるので、GCのオーバーヘッドを削ることができるかもしれない。一方で、スタックチャンクを小さくしすぎると、チャンク間のオーバーフロー/アンダーフローが増えるので、オーバーヘッドが増える。32kというデフォルト設定は、大部分の場合においてそれなりな妥協点のようである。

-kb size

[デフォルト: 1k] スタックチャンクバッファの大きさを設定する。スタックチャンクがオーバーフローして新しいチャンクが作成される時、前のスタックのデータの一部が新しいチャンクに移動される。これは、即座にアンダーフローが発生し、境界でオーバーフロー/アンダーフローが繰り返されるのを防ぐためである。ここで動かされるスタックの量が-kbによって設定される。

空間を無駄にするのを防ぐために、この値は典型的にはスタックチャンクの大きさ(-kc)の10%未満に設定されるべきである。スタックチャンクの連鎖の中で、それぞれのチャンクがこの大きさの未使用の空間を隙間として持つことになるからである。

-K size

[デフォルト: 物理メモリの大きさの80%] 個々のスレッドの最大スタックサイズをsizeに設定する。スレッドがこの制限を超えようとした場合、StackOverflow例外が送られる。sizeとして0を指定すると、この検査は完全に無効になる。

このオプションは主に、プログラムが無限ループに陥ったときに利用可能なメモリを使い果たすことがないようするために存在している。

-m n

ヒープのうち確保可能な(訳注: 未使用な)部分の最小%を指定する。デフォルトは3%である。

-M size

[デフォルト: 無制限] 最大ヒープサイズをsizeに設定する。ヒープは通常プログラムのメモリ要求に従って伸長したり収縮したりする。このオプションの唯一の存在理由は、ヒープが際限なく膨張してスワップ空間を使い果たす(結果として、少なくともプログラムが即座にOSに殺されることになる)のを防ぐことである。

ヒープの最大サイズは他のGCパラメタにも影響する。ヒープ中の生存データが最大ヒープサイズに対して一定の割合を越えると、最古の世代に対して自動的にコンパクト化回収が有効になり、最大ヒープサイズを越えることがないように-Fパラメタが減少せしめられる。

-T , -t[file] , -s[file] , -S[file] , --machine-readable

これらのオプションはランタイムシステムの統計情報を生成する。プログラムを実行するのに使った時間、ガベッジコレクタで使った時間、確保されたメモリの量、ヒープの最大サイズ、といったものである。この三種類のオプションは、詳細さの度合いが異なる。-Tはデータを収集するものの出力を生成しない。-tはGHCの-Rghc-timingオプションと同じ形式で一行だけの出力を生成する。-sはプログラムの最後にもっと詳細なまとめを生成しする。-Sはさらにガベッジコレクション一回一回について情報を生成する。

出力内容はfileに置かれる。fileが省略された場合、出力はstderrに送られる。

-Tフラグを使った場合、統計にアクセスするにはGHC.Statsを使う。

-tフラグを使った場合、プログラムの終了時、次のようなものを見ることになるだろう。

<<ghc: 36169392 bytes, 69 GCs, 603392/1065272 avg/max bytes residency (2 samples), 3M in use, 0.00 INIT (0.00 elapsed), 0.02 MUT (0.02 elapsed), 0.07 GC (0.07 elapsed) :ghc>>

これは以下のことを伝えている。

  • この実行全体にわたってプログラムによって確保されたバイトの総量。

  • 実行されたガベッジコレクションの合計回数。

  • 「内容量(residency)」の平均値および最大値。内容量とは、生存しているデータの量をバイト単位で表したものである。ランタイムが生存データの量を決定できるのは大GCの間だけなので、標本の数が大GCの数に対応する(これは比較的小さい数になる)。プログラムのヒープの性質についてのよりよい描像を得るためには、-hTを使うこと(4.17.5. プロファイルに関するRTSオプション)。

  • RTSがOSから確保したメモリの最大値。

  • ランタイムシステムの初期化(INIT)、プログラム自体の実行(MUT, the mutator)、ガベッジコレクション(GC)それぞれのCPU時間と経過した実時間。

-t --machine-readableとすれば、よりfuture-proofな、機械可読の形式でこれを得ることができる。

[("bytes allocated", "36169392")
,("num_GCs", "69")
,("average_bytes_used", "603392")
,("max_bytes_used", "1065272")
,("num_byte_usage_samples", "2")
,("peak_megabytes_allocated", "3")
,("init_cpu_seconds", "0.00")
,("init_wall_seconds", "0.00")
,("mutator_cpu_seconds", "0.02")
,("mutator_wall_seconds", "0.02")
,("GC_cpu_seconds", "0.07")
,("GC_wall_seconds", "0.07")
]

-sフラグを使った場合、プログラムの終了時に次のようなものを目にすることになるだろう。(細部が厳密にどうなるかは使っているRTSの種類によって異なる。例えば、プロファイルのデータを見られるのはRTSがプロファイル用にコンパイルされている場合だけである)

    36,169,392 bytes allocated in the heap
     4,057,632 bytes copied during GC
     1,065,272 bytes maximum residency (2 sample(s))
        54,312 bytes maximum slop
             3 MB total memory in use (0 MB lost due to fragmentation)

Generation 0:    67 collections,     0 parallel,  0.04s,  0.03s elapsed
Generation 1:     2 collections,     0 parallel,  0.03s,  0.04s elapsed

SPARKS: 359207 (557 converted, 149591 pruned)

INIT  time    0.00s  (  0.00s elapsed)
MUT   time    0.01s  (  0.02s elapsed)
GC    time    0.07s  (  0.07s elapsed)
EXIT  time    0.00s  (  0.00s elapsed)
Total time    0.08s  (  0.09s elapsed)

%GC time      89.5%  (75.3% elapsed)

Alloc rate    4,520,608,923 bytes per MUT second

Productivity  10.5% of total user, 9.1% of total elapsed
  • "bytes allocated in the heap"はこの実行全体にわたってプログラムによって確保されたバイトの総量である。

  • GHCはデフォルトではコピーGCを使っている。"bytes copied during GC"はガベッジコレクション中にコピーしなければならなかったバイト数を示す。

  • プログラムによって実際に使われた空間の最大値が"bytes maximum residency"の数値である。これは大GCのときにしか検査されないから、単なる近似値である。標本(sample)の数によって何回検査されたかが分かる。

  • "bytes maximum slop"は、GHCがメモリをブロック単位で確保することによって無駄になった空間の最大値である。 slopとはブロックの無駄になった後方部分である。これを制御する方法はない。どれだけのメモリがこうやって失われたか見たいだけである。

  • "total memory in use"はRTSがOSから確保したメモリの最大値である。

  • 次に、行われたガベッジコレクションについての情報がある。それぞれの世代について、ガベッジコレクションが何回行われたか、そのうち何回が並列に行われたか、その世代をGCするために使われたCPU時間の合計、その世代をGCするために経過した実時間の合計、が示されている。

  • 統計情報SPARKSは、プログラム中でのControl.Parallel.parやこれに関連した機能の利用に関係する。個々のスパーク(spark)はparの一回の呼び出しを表す。スパークが「変換される(converted)」とは、それが並列に実行されることである。スパークが「刈り取られる(pruned)」とは、それが既に評価済みであることが分かり、ガベッジコレクタによってプールから捨てられることである。実行が終わると残っているスパークはすべて捨てられるので、「変換された」ものと「刈り取られた」ものを足しても合計に見たないことがあり得る。

  • 次に、CPU時間と経過した実時間を、そのときランタイムシステムが何をしていたかによって項目分けしたものがある。INITはランタイムシステムの初期化である。MUTはmutator time、すなわち実際にあなたのコードを走らせるのに掛かった時間である。GCはガベッジコレクションを行うのに掛かった時間である。RPは維持原因プロファイルを行うのに掛かった時間である。PROFはその他のプロファイルを行うのに掛かった時間である。EXITはランタイムシステムの終了処理時間である。最後に、Totalは、もちろん、合計である。

    %GCは、GCが全体の何%を占めるかを表している。"Alloc rate"は"bytes allocated in the heap"をMUT CPU時間で割ったものである。"Productivity"はCPU時間および経過した実時間の合計の何%がmutator(MUT)で消費されたかである。

-Sフラグは、-sフラグと同じものを出力するのに加え、GCが発生するたびにそれについての情報を表示する。

    Alloc    Copied     Live    GC    GC     TOT     TOT  Page Flts
    bytes     bytes     bytes  user  elap    user    elap
   528496     47728    141512  0.01  0.02    0.02    0.02    0    0  (Gen:  1)
[...]
   524944    175944   1726384  0.00  0.00    0.08    0.11    0    0  (Gen:  0)

個々のガベッジコレクションについて、表示するのは以下のものである。

  • このGCで確保したバイト数。

  • このGCでコピーしたバイト数。

  • 現在生存しているバイト数。

  • このGCに掛かった時間(CPU時間および経過した実時間)。

  • これまでにプログラムが実行された時間(CPU時間および経過した実時間)。

  • このGCで起こったページフォールトの個数。

  • 直近のGCの終了以降に起こったページフォールトの個数。

  • GC対象の世代。

4.17.4. 並行性と並列性に関するRTSオプション

並行実行に関わるRTSオプションは4.14. Concurrent Haskellを使うで、並列計算に関わる物は4.15.2. SMP並列性のためのRTSオプションで、それぞれ説明されている。

4.17.5. プロファイルに関するRTSオプション

プロファイルについてのランタイムオプションの大部分は、プログラムをプロファイル用にコンパイルした場合にのみ利用可能である。(5.2. プロファイルについてのコンパイルオプションを見よ、またランタイムオプションについては5.4.1. ヒーププロファイルのためのRTSオプションを見よ)。ただし、通常の非プロファイル版実行ファイルでも利用できるプロファイルオプションがただ一つ存在する。

-hT

(-hに短縮可。) 基本的なヒーププロファイルを、prog.hpというファイルに生成する。ヒーププロファイルのグラフを作成するには、hp2ps(5.5. hp2ps--ヒーププロファイルをPostScriptへを見よ)を使えばよい。この基本的なヒーププロファイルは、データ構築子ごとに分類され、その他の種類のクロージャはFUNTHUNKといった広い範疇ごとにまとめられている。より詳細なプロファイルを得るには、完全なプロファイルサポート(第5章. プロファイルを取る)を使えばよい。

4.17.6. 追跡情報を得る

プログラムが-eventlogオプション(4.12.6. リンクに影響するオプション)付きでリンクされている場合、実行時のイベントを二通りの方法で記録することができる。

  • バイナリ形式でファイルに記録し、後でいろいろなツールによって分析できるようにする。ThreadScopeはそのようなツールの一つであり、イベント記録を解釈して、プログラムの視覚的な並列実行プロファイルを作成する。

  • テキストとして標準出力に出力し、デバッグに供する。

-l[flags]

イベントをバイナリ形式でprogram.eventlogというファイルに記録する。flagsが指定されなかった場合、ThreadScopeなどのツールに適したデフォルトのイベント集合を記録する。

特殊な利用状況では、どのイベントを含むかをもっと細かく制御したいかもしれない。flagsは、どの種類のイベントを記録するかを意味する文字を零個以上並べたものである。現在、有効/無効にできるイベントの種類は以下である。

s — スケジューラのイベント、スレッドの作成、開始/停止のイベントを含む。デフォルトで有効。
g — GCのイベント、GCの開始/停止を含む。デフォルトで有効。
p — 並列スパーク(標本抽出)。デフォルトで有効。
f — 並列スパーク(完全に正確)。デフォルトで無効。
u — ユーザイベント。Debug.Trace.traceEventなどの関数を使ってHaskellコードから出力されるイベントである。デフォルトで有効。

特定の種類を無効にしたり、全ての種類を一度に有効/無効にしたりすることができる。

a — 上で挙げた全ての種類のイベントを有効にする
-x — 与えられた種類のイベントを無効にする。上で挙げた種類をどれか指定するか、-aなら全てのイベントになる。

例えば、-l-agはGCイベント(g)を除いて全てのイベント(-a)を無効にする。

スパークのイベントには二つのモードがある。標本抽出と完全に正確である。個々のスパークの生涯にはいろいろなイベントがある。通常は作成と実行だけだが、もっと例外的なものもあり得る。標本抽出モードでは、短い間隔で、それぞれのスパークイベントの数が標本抽出される。完全に正確モードでは、全てのスパークが個々に記録される。後者の方が実行時のオーバーヘッドが高く、デフォルトでは有効になっていない。

記録ファイルの形式はGHCに付属するEventLogFormat.hに記述されており、Haskellではghc-eventsライブラリを使うことでパースできる。.eventlogファイルの内容をテキストとしてダンプするには、ghc-eventsパッケージに付属しているghc-events showというツールが使える。

-v[flags]

イベントを、.eventlogファイルにではなく、テキストとして標準出力に記録する。flags-lの場合と同じであるが、追加のオプションtがあって、これは、表示される個々のイベントの前にタイムスタンプを表示することを示す。(バイナリの.eventlogファイルでは、全てのイベントは自動的にタイムスタンプと結び付けられる)

この追跡フレームワークで記録されるイベントを、デバッグオプション-Dxも生成する。デフォルトではこれらのイベントはテキストとして標準出力にダンプされる(-Dxによって-vが有効になる)が、-lを使うことでバイナリのイベント記録ファイルに保存することもできる。

4.17.7. ハックする者、デバッグする者、及び好奇心過剰な魂のためのRTSオプション

これらのRTSオプションは、(a) GHCのバグを回避するために、(b) 「何が実際に起きているか」を知るために、(c) なんとなくそうしたいから、という理由で使うことができる。素人にはおすすめできない。

-B

GC(大)が始まるたびにベルを鳴らす。

実に奇妙なことだが、実際にこのオプションを使う人々が存在する。ダラム(イギリス)にいる我々の仲間、ポール・キャラハンは次のように書いている。「ここには色んな目的のためにこれを使う人々がいる—これは本当だ—例えば、コード/機械が何かをしているということを確認したり、無限ループを発見したり、最近付け加えたコードのコストを量ったりだ。ある種の人々はビープのパターンからプログラムがどの段階にいるかを聞き分けることさえできる。しかし最も重要な使いかたは、同じオフィスにいる他の人を鬱陶しがらせることである…」

-D x

RTSのデバッグフラグ。RTSがDEBUGオプション付きでリンクされているときのみ使える。RTSのいろいろな部分について、デバッグ出力や追加の実行時の正気チェックを有効にするためのxの値が用意されている。例えば、+RTS -Ds -RTSとするとスケジューラのデバッグ出力が有効になる。どういうデバッグフラグがサポートされているかを知るには+RTS -?が使える。

-lオプションを追加すると、デバッグ出力が標準出力でなくバイナリのイベント記録ファイルに送られるようになる。デバッグ追跡にかかるオーバーヘッドを減らしたいときに便利かもしれない。

-r file

プログラムの実行終了時に「ticky-ticky」統計情報を生成する(プログラムが-debugオプション付きでリンクされている場合にのみ利用可能)。fileの扱いは上にある-SRTSオプションの場合と同じである。

ticky-tickyプロファイルについての更なる情報は、5.8. 「ticky-ticky」プロファイルを使うを見よ。

-xc

(プログラムがプロファイルを取れるようにコンパイルしてある場合のみ利用可能) プログラム中で例外が発生したとき、このオプションが指定されていると、スタックトレースがstderrに出力される。

これは特にデバッグ時に有用である。プログラムがhead []エラーを起こすが、コードのどの部分が原因か分からないとき、-prof -fprof-autoを付けてコンパイルし、+RTS -xc -RTSを付けて実行すれば、エラーが発生した地点の呼び出しスタックが正確に分かる。

出力には、プログラムで発生した例外それぞれ(プログラムは、実行を通して、例外を複数回発生させたり捕捉したりし得る)につき一つの報告を含む。個々の報告は以下のようなものである。

*** Exception raised (reporting due to +RTS -xc), stack trace:
  GHC.List.CAF
  --> evaluated by: Main.polynomial.table_search,
  called from Main.polynomial.theta_index,
  called from Main.polynomial,
  called from Main.zonal_pressure,
  called from Main.make_pressure.p,
  called from Main.make_pressure,
  called from Main.compute_initial_state.p,
  called from Main.compute_initial_state,
  called from Main.CAF
  ...

スタックトレースはしばしば、GHC.List.CAFのような役に立たないものから始まる。これはGHCの最適化器が例外を最上位に移した痕跡である。これに対してプロファイルシステムはコスト集約点「CAF」を割り当てた。しかし+RTS -xcは単に現在のスタックを表示するだけでなく、より深く見てそのCAFが評価された時点のスタックを報告する。CAFでないスタックが見つかるまでさらにスタックを報告するかもしれない。上の例では、次のスタック(--> evaluated byの後)が、プログラムがhead []を評価したときに何をしていたかについて、沢山の情報を含んでいる。

実装の詳細はさておき、スタック中の関数名は願わくばバグを見つけだすのに十分な手掛りをあなたに与えるだろう。

呼び出しスタックを見る別の方法については、Debug.TraceモジュールのtraceStack関数も見よ。

-Z

GC時の「update-frame-squeezing」を無効にする。サンクへの進入回数(entry count)についてのある種のデータを正確に収集したい場合を除いて、これを無効にする理由はあまりない。

4.17.8. RTSに関する情報を取得する

RTSに、自分自身の情報をいくらか表示させることが可能である。これをするには--infoフラグを使う。例えば次のように。

$ ./a.out +RTS --info
 [("GHC RTS", "YES")
 ,("GHC version", "6.7")
 ,("RTS way", "rts_p")
 ,("Host platform", "x86_64-unknown-linux")
 ,("Host architecture", "x86_64")
 ,("Host OS", "linux")
 ,("Host vendor", "unknown")
 ,("Build platform", "x86_64-unknown-linux")
 ,("Build architecture", "x86_64")
 ,("Build OS", "linux")
 ,("Build vendor", "unknown")
 ,("Target platform", "x86_64-unknown-linux")
 ,("Target architecture", "x86_64")
 ,("Target OS", "linux")
 ,("Target vendor", "unknown")
 ,("Word size", "64")
 ,("Compiler unregisterised", "NO")
 ,("Tables next to code", "YES")
 ]

この情報は、[(String, String)]型の値として読めるように整形されている。現在、以下のフィールドが存在する。

GHC RTS

このプログラムはGHC RTSにリンクされているか?(常に"YES")

GHC version

このプログラムをコンパイルするのに用いられたGHCのバージョン

RTS way

ランタイムの種類(「way」)。特によくある値は、rts_v(バニラ)、rts_thr(スレッド化されたランタイム、つまり-threadedオプション付きでリンクされたもの)、rts_p(プロファイル用ランタイム、つまり-profオプション付きでリンクされたもの)である。他の種類には、debug(-debugを使ってリンクされたもの)、dyn(RTSが動的にリンクされる、つまり実行ファイルに静的にリンクされているのでない共有ライブラリである)などがある。これらは組み合わせることができる。例えば、rts_thr_debug_pというものが可能である。

Target platform, Target architecture, Target OS, Target vendor

プログラムが走ることを意図されたプラットフォーム。

Build platform, Build architecture, Build OS, Build vendor

プログラムがビルドされたプラットフォーム。(つまり、GHC自身のターゲットプラットフォームである)。通常これはターゲットプラットフォームと同じである。(クロスコンパイルの際には異なることが考えられる)

Host platform, Host architecture Host OS Host vendor

GHC自身がコンパイルされたプラットフォーム。これも、通常はビルドおよびターゲットのプラットフォームと同じである。

Word size

ターゲットプラットフォームのワードの大きさに応じて、"32"または"64"

Compiler unregistered(訳注: unregisteredでなくunregisterisedが正しいものと思われる)

このプログラムは「非レジスタ化」版のGHC(すなわち、通常未対応のプラットフォームであるために、プラットフォーム固有の最適化なしでコンパイルされたGHC)によってコンパイルされたか?実験的なGHCのビルドを使っているのでない限り、この値は通常noである。

Tables next to code

info tableをコードに直接隣接させて配置するという最適化は有用であるが、全てのプラットフォームで対応されている訳ではない。このフィールドは、プログラムがこの最適化を使ってコンパイルされているかどうかを示すものである。(珍しいプラットフォームでなければ通常yesである)