2015-12-17 6 views
5

60GB以上のテキストファイルを処理しています。これらのファイルは、可変長のヘッダーセクションとデータセクションに分かれています。 Clojure - メモリ不足の膨大なファイルを処理する

  • 処理機能が非同期アクセスして変更データ線
  • process-headerプロセスつのヘッダ行の文字列
  • process-dataプロセス一つのデータ線列からヘッダ行を区別する

    • head?述語:私は3つの機能を有していますメモリ内のデータベース

    別のSOスレッドからファイルの読み取り方法を進めました。怠惰な一連の行。 1つの関数でいくつかの行を処理し、関数を1回切り替えて、次の関数で処理を続けるという考えがありました。

    (defn lazy-file 
        [file-name] 
        (letfn [(helper [rdr] 
          (lazy-seq 
          (if-let [line (.readLine rdr)] 
           (cons line (helper rdr)) 
           (do (.close rdr) nil))))] 
        (try 
         (helper (clojure.java.io/reader file-name)) 
         (catch Exception e 
         (println "Exception while trying to open file" file-name))))) 
    

    が、私はそれが動作しますが、それはいくつかの理由ではなく、非効率的だ

    (let [lfile (lazy-file "my-file.txt")] 
        (doseq [line lfile :while head?] 
        (process-header line)) 
        (doseq [line (drop-while head? lfile)] 
        (process-data line))) 
    

    ようなもので、それを使用します。私は、データに到達するまで、単にprocess-headを呼び出す代わりに

    • process-dataを続行すると、ヘッダー行をフィルタ処理して処理した後、ファイル全体の解析を再開し、すべてのヘッダー行を処理してデータを処理する必要があります。これは、lazy-fileが意図したものとまったく反対です。
    • メモリ消費量を見れば、プログラムはメモリにファイルを保持するのに必要な量のRAMを使用するように構築されています。

    私のデータベースを使用すると効率的で慣用的な方法はありますか?

    head?述部の値に応じてヘッダーとデータを処理するには、マルチメソッドを使用することが考えられますが、これは特に重要なスピードに影響すると考えられます。常に真実に当てはまる。私はそれをまだベンチマーキングしなかった。

    line-seqを構築してiterateと解析する方が良いでしょうか?これはまだ私に使用する必要があります:whileと:drop-while、私は思います。

    私の研究では、NIOファイルアクセスを使用することが数回言及されています。これはメモリ使用量を改善するはずです。私はそれを卒法で慣用的な方法で使用する方法をまだ見つけられませんでした。

    多分私はまだ一般的なアイデアの悪い把握を持っている、どのようにファイルを処理する必要がありますか?

    いつものように、どんな助けやアイデア、ツイートへのポインタも大歓迎です。

  • 答えて

    0

    ここで考慮すべきいくつかのものがあります。

    1. メモリ使用量

      doseqが、具体的には上保持していないものの、Leiningenをは、頭部への参照を維持することになるものが追加される場合があります報告がありますそれが処理しているシーケンスの先頭、cf. this SO questionlein replを使わずに、ファイルをメモリに保持するのに必要な量のRAMを使用するという主張を検証してみてください。代わりにdoseqで2つのループを使用してのライン

      の解析

    2. は、あなたもloop/recurアプローチを使用することができます。あなたがパースされることを期待することは、このような第二引数(未テスト)のようになります。

      (loop [lfile (lazy-file "my-file.txt") 
            parse-header true] 
           (let [line (first lfile)] 
            (if [and parse-header (head? line)] 
             (do (process-header line) 
              (recur (rest lfile) true)) 
             (do (process-data line) 
              (recur (rest lfile) false))))) 
      

      別のオプションは、あなたのファイル読み込み機能にあなたの処理機能を組み込むことであろうその、ここにあります。したがって、新しい行を入力して返すだけでなく、すぐに処理することもできます。通常、ハードコーディングではなく、引数として処理関数を渡すことができます。

      あなたの現在のコードは処理のように見えますが、副作用です。もしそうならば、処理を組み込むならば、おそらく怠惰を避けることができます。とにかくファイル全体を処理する必要があります(またはそう思われます)。また、行単位で処理する必要があります。 lazy-seqのアプローチでは、基本的に、単一の行を単一の処理呼び出しで並べ替えるだけです。現在のソリューションでは、読み込み(ファイル全体を1行ずつ)と処理を分離するため、怠惰に対するニーズが生じます。代わりに、行の処理を読みに移動する場合は、その行を遅延させる必要はありません。

    +0

    感謝。昨日私はベンチマークを行うためにいくつかのテストケースを書いた。それはそのことが判明しました ** A)**それは多くのメモリを消費する読み込み自体ではなく、データベース(btw、私のメモリ消費クレームは、コンパイルされたアプリケーションを実行することから来ているようです) ** B)*驚くべきことに、マルチメソッドとループ再帰のアプローチでは、約150%が必要になるでしょう。ファイルを2回開き、/ drop-whileの間に使用するのに必要な時間。 – waechtertroll

    +0

    ファイルを読んでいる間にあなたのやり方が気に入っています。次の行がデータ行(イテレータースタイル)であるかどうかヘッダーパーサーチェックを行い、そうであれば、データパーサーからトランポリンを離れて試してみてください。各行のIf-elseは本当に遅いですが、ファイルは数百のヘッダー行と数億のデータ行に明確に定義されており、頭の読み取りには0.5秒もかかりません。私はまだ、トランポリンとイテレータをどのように組み合わせるかわからない... – waechtertroll

    2

    標準ライブラリ関数を使用する必要があります。

    line-seq、with-openとdoseqは簡単に作業を行います。行に

    何か:あなたの答えのための

    (with-open [rdr (clojure.java.io/reader file-path)] 
        (doseq [line (line-seq rdr)] 
        (if (head? line) 
         (process-header line) 
         (process-data line)))) 
    
    +0

    あなたの提案をありがとう。私が使用している '' lazy-file''メソッドは、私がclojureを学習し始めたときに実装され、ioモジュールに格納されてそこから使用されました。それの正味の効果は真に '' '' line-seq'''を使うこととまったく同じです。 – waechtertroll

    +0

    もう一つの副次的な情報であるif-elseアプローチは、私が取っていたよりもかなり遅い(1.5倍)ことが判明しました。ここでのランタイムは時間単位で測定されているので、かなり重要です。 – waechtertroll

    +0

    'lazy-file'についてのあなたの議論を理解していますが、この関数を単体テストにするのが難しくなります。 – kawas44

    関連する問題