46

現在、Snapに書かれた小さなHaskell Webサーバーを試しています。このサーバーは、クライアントに多くのデータをロードして利用可能にします。そして、私は非常に苦労してサーバプロセスをコントロールしています。ランダムな瞬間に、プロセスは数秒から数分間CPUを大量に使用し、クライアントの要求に無関係になります。場合によっては、メモリ使用量が数百メガバイト(秒)以内に急増(時には低下することがあります)します。Haskellで5GBのヒープを制御するには?

多くのメモリを使用する、長時間実行されているHaskellプロセスでは、誰かがより安定したものにするためのいくつかの指針を与えることができればうれしいです。私は数日の間デバッグしてきました、そして、私はここで少し切望し始めています。

私のセットアップの小さな概要:私は、メモリの大(ネスト)Data.Map-似た構造へのデータのおよそ5ギガバイトを読んで、サーバの起動時に

  • 。ネストされたマップはvalue strictで、マップ内のすべての値はすべてのフィールドが厳密に設定されたデータ型です。私は未評価のサンクが残っていないことを保証するのに多くの時間をかけました。インポート(システムの負荷によって異なります)には5〜30分かかります。奇妙なことは、連続走行の変動が予想以上に大きいことですが、それは別の問題です。

  • 大きなデータ構造は、スナップサーバーによって生成されたすべてのクライアントスレッドによって共有される 'TVar'の内部に存在します。クライアントは、小さなクエリ言語を使用してデータの任意の部分を要求できます。データ要求の量は通常(わずか300kbかそれまで)小さく、データ構造の小さな部分にしか触れません。すべての読み取り専用要求は 'readTVarIO'を使用して行われるため、STMトランザクションは必要ありません。

  • サーバーは、次のフラグで起動します。+ RTS -N -I0 -qg -qb。これにより、マルチスレッドモードでサーバが起動し、アイドル時間とパラレルGCが無効になります。これはプロセスを大幅に高速化するようです。

サーバーは、ほとんど問題なく動作します。しかし、今はクライアントリクエストがタイムアウトしてCPUが100%(または100%以上)スパイクして、これを長時間続けます。一方、サーバーは要求に応答しません。

私はそのことを考えることができますいくつかの理由のCPU使用率を引き起こす可能性があります。

  • やるべき仕事がたくさんあるので、要求だけで多くの時間を要するが。これは、以前の実行で非常に高速であることが判明しているリクエスト(高速では20-80ms程度)で発生することがあるため、場合によっては起こりにくいです。

  • データが処理されてクライアントに送信される前に、計算される必要がある未評価のサンクがまだあります。これはまた、前の点と同じ理由で、起こりそうもありません。

  • 何とかガベージコレクションが開始され、5GBのヒープ全体がスキャンされます。私はこれが多くの時間を取ることができると想像することができます。

問題は、正確に何が起こっているのか、これについて何をすべきかを理解する手がかりがないことです。インポート処理にかかる時間が長くかかるので、プロファイリングの結果は何も役に立たない。条件付きでコード内からプロファイラをオン/オフする方法はないようです。

個人的にGCが問題であると思われます。私はGHC7を使用しています.GHC7には、GCの動作方法を調整するオプションがたくさんあるようです。

一般的に非常に安定したデータを持つ大きなヒープを使用する場合、どのようなGC設定を推奨しますか?

+1

おかげで..どのくらいのRAMこのサーバーアプリケーションを実行しているボックス – Ankur

+0

私のマシン上に合計8GBのRAMがあります。それで十分でしょう。 –

+0

ええ、それはページフォールトを避けるのに十分だと思われます – Ankur

答えて

29

大容量メモリの使用状況と時折CPUスパイクは、ほぼ確実に蹴りGCである。これは確かにメジャーコレクションがあるたびにビープ音を鳴らしGHCの原因-BのようなRTSオプションを使用して場合、あるかどうかを確認することができ、-tれます事実の後で(特にGC時間が本当に長いかどうか)、または-Dgの後に統計を教えてください。これにより、GC呼び出しのデバッグ情報が有効になります(-debugでコンパイルする必要があります)。あなたはこの問題を軽減するために行うことができますいくつかあります

:データの最初のインポートに

  • は、GHCは、ヒープの成長に多くの時間を無駄にしています。 -Hを大きく指定することで、一度に必要なすべてのメモリを確保することができます。

  • 安定したデータを持つ大きなヒープは古い世代に昇格します。 -Gで世代数を増やした場合、安定性の高いデータを最も古い世代、非常にまれなGC世代の世代にすることができます。

  • 残りのアプリケーションのメモリ使用量に応じて、-Fを使用して、GHCで古い世代をどれだけ増やしてから再び収集するかを調整できます。このパラメータを調整して、これをガベージコレクションしないようにすることができます。

  • 書き込みがなく、インターフェイスが明確であれば、このメモリをGHC(C FFIを使用)で管理しないようにする価値がありますので、スーパーGCの可能性はありません。

これらはすべて投機的なので、特定のアプリケーションでテストしてください。

+5

圧縮GCの使用も有用です。 –

+0

まだ徹底的にテストしていませんが、世代数を増やしても効果があるようです。 –

2

ネストされたマップの1.5GBのヒープで非常に似た問題がありました。アイドル状態のGCをデフォルトでオンにすると、すべてのGCで3〜4秒のフリーズが発生し、アイドルGCオフ(+ RTS -I0)で数百回のクエリ後に17秒のフリーズが発生し、クライアント時間が発生します-でる。

私の「解決策」は最初にクライアントのタイムアウトを増加させ、クエリの98%が約500msであったのに対し、クエリの約2%が遅いと考えていました。しかし、より良い解決策を求めて、ロードバランシングされた2台のサーバーを実行し、クラスタから200個のクエリごとにperformGCをオフラインにしてから、元の状態に戻しました。

これは傷ついたことを追加して、これは決してこのような問題を抱えていないオリジナルのPythonプログラムを書き直したものです。公平で、私たちは約40%のパフォーマンスの向上、簡単な並列化、より安定したコードベースを得ました。しかし、この厄介なGCの問題...

関連する問題