attoparsecは、大規模な「テイク」コールで大量のメモリを割り当てます

StackOverflow https://stackoverflow.com/questions/4151265

  •  08-10-2019
  •  | 
  •  

質問

だから私はパケットスニッフィングアプリを書いています。基本的に、TCPセッションのためにスニッフィングしてから、それらがHTTPであるかどうか、そしてそれらが適切なコンテンツタイプなどがあるかどうかを確認して、ハードドライブのファイルとして保存しました。

だから、そのために、私はそれが効率的であることを望んでいました。現在のHTTPライブラリは文字列ベースであり、私は大きなファイルを扱うので、HTTP応答を解析するだけで本当に必要なので、私はAttoparsecで自分のものをロールすることにしました。

プログラムを終了したとき、WAVファイルを含む9メグのHTTP応答を解析しているとき、プロファイルしたとき、HTTP応答のボディを解析しようとしているときにメモリのギグを割り当てることがわかりました。 。 http.profを見ると、いくつかの行が表示されます。

httpBody              Main                                                 362           1   0.0    0.0    93.8   99.3

 take                 Data.Attoparsec.Internal                             366        1201   0.0    0.0    93.8   99.3
     takeWith            Data.Attoparsec.Internal                             367        3603   0.0    0.0    93.8   99.3
      demandInput        Data.Attoparsec.Internal                             375         293   0.0    0.0    93.8   99.2
       prompt            Data.Attoparsec.Internal                             378         293   0.0    0.0    93.8   99.2
        +++              Data.Attoparsec.Internal                             380         586  93.8   99.2    93.8   99.2

あなたが見ることができるように、HTTPBody内のどこかで、テイクは1201回と呼ばれ、バイテストリングの500+(+++)連結を引き起こし、それが不条理な量のメモリ割り当てを引き起こします。

これがコードです。 nは、HTTP応答のコンテンツの長さにすぎません。ない場合は、すべてを取ろうとします。

1000程度の文字バイテストリングの怠zyなバイテストリングを返すことを望んでいましたが、nを取るだけで厳格なバイテストリングを返すように変更しても、それらにそれらの割り当てがあります(そして、14ギグのメモリを使用します)。


httpBody n = do
  x <- if n > 0
    then AC.take n
    else AC.takeWhile (\_ -> True)
  if B.length x == 0
    then return Nothing
    else return (Just x)

私はCombinatorrentを行った男のブログを読んでいて、彼は同じ問題を抱えていましたが、解決策を聞いたことはありませんでした。誰かがこの問題に出くわしたことがありますか、それとも解決策を見つけたことがありますか?

編集:わかりました、まあ私はこれを一日中去り、何も得ませんでした。問題を調査した後、AttopArsecに怠zyなバイテストリングアクセサを追加せずにそれを行う方法はないと思います。私は他のすべてのライブラリも見て、彼らはバイテストリングや他のものを欠いていました。

だから私は回避策を見つけました。 HTTPリクエストについて考えると、ヘッダー、Newline、Newline、Bodyになります。体は最後であり、解析はあなたが解析したものとバイテストリングの残りの両方でタプルを返しているので、私はattoparesecの内側の体をスキップして、代わりに残っているバイテストリングから体をまっすぐに引き抜くことができます。


parseHTTPs bs = if P.length results == 0
  then Nothing
  else Just results
  where results = foldParse(bs, [])

foldParse (bs,rs) = case ACL.parse httpResponse bs of
  ACL.Done rest r -> addBody (rest,rs) r
  otherwise ->  rs

addBody (rest,rs) http = foldParse (rest', rs')
  where
    contentlength = ((read . BU.toString) (maybe "0" id (hdrContentLength (rspHeaders http))))
    rest' = BL.drop contentlength rest
    rs' = rs ++ [http { rspBody = body' }]
    body'
      | contentlength == 0  = Just rest
      | BL.length rest == 0 = Nothing
      | otherwise           = Just (BL.take contentlength rest)
httpResponse = do
  (code, desc) <- statusLine
  hdrs <- many header
  endOfLine
--  body <- httpBody ((read . BU.toString) (maybe "0" id (hdrContentLength parsedHeaders)))

  return Response { rspCode = code, rspReason = desc, rspHeaders = parseHeaders hdrs,  rspBody = undefined }

少し乱雑ですが、最終的には速く動作し、私が望んでいた以上のものを割り当てます。したがって、基本的には、HTTPデータ構造を収集するバイテストリング上で折りたたまれ、コレクションの合間に、取得した構造のコンテンツの長さを確認し、残りのバイテストリングから適切な量を引き出し、バイテストリングが残っている場合は続行します。

編集:私は実際にこのプロジェクトを完了しました。魅力のように機能します。私は適切にカバリズ化されていませんが、誰かがソース全体を見たい場合は、でそれを見つけることができます https://github.com/onmach/audio-sniffer.

役に立ちましたか?

解決

ここにcombinatorrentの男:)

メモリが役立つ場合、attoparsecの問題は、一度に少しずつ入力を要求し、最終的に連結される怠zyなバイテストリングを構築することです。私の「解決策」は、入力関数を自分で転がすことでした。つまり、ネットワークソケットからattoparsecの入力ストリームを取得し、メッセージで期待するバイトの数を知っています。基本的に、私は2つのケースに分割されました。

  • メッセージは小さい:ソケットから最大4kまで読み、一度に少しずつそのバイテストを食べます(バイテストリングのスライスは速く、疲れ果てた後に4Kを捨てます)。

  • メッセージは「大きい」(ここではBittorrent Speakで約16キロバイトを意味します):私たちは、私たちが持っている4Kチャンクがどれだけの成果を上げることができるかを計算し、その後、基礎となるネットワークソケットに物事を埋めるように要求します。 4Kチャンクの残りの部分と大きなチャンク。彼らはすべてのデータを持っているので、それらを連結してそれらを解析することは私たちがしていることです。

    連結を最適化できる場合があります。

TL; DRバージョン:ATTOPARSECの外でそれを処理し、問題を回避するためにループを操作します。

関連するcombinatorrentコミットはFC131FE24です

https://github.com/jlouis/combinatorrent/commit/fc131fe24207909dd980c674aae6aaba27b966d4

詳細については。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top