2011-11-12 8 views
1

大規模なシーケンスでloop/recurを使用すると、怠惰なシーケンスがOutOfMemoryErrorを引き起こす可能性があることを読んでいます。私はそれを処理するためにメモリから3MBのファイルをロードしようとしています。そして、これは私に起こっていると思います。しかし、それを修正するための慣用的な方法があるかどうかはわかりません。私はdoallを入れてみましたが、私のプログラムは終了していないようです。小さな入力は働く:Clojure OutOfMemoryError

小さな入力(ファイルの内容):AAABBBCCC 正しい出力:((65 65)(65 66)(66 66)(67 67)(67 67))

コード:

(def file-path "/Users/me/Desktop/temp/bob.txt") 
;(def file-path "/Users/me/Downloads/3MB_song.m4a") 

(def group-by-twos 
    (fn [a-list] 
    (let [first-two (fn [a-list] (list (take 2 a-list))) 
      the-rest-after-two (fn [a-list] (rest (rest a-list))) 
      only-two-left? (fn [a-list] (if (= (count a-list) 2) true false))] 
     (loop [result '() rest-of-list a-list] 
     (if (nil? rest-of-list) 
      result 
      (if (only-two-left? rest-of-list) 
      (concat result (list rest-of-list)) 
      (recur (concat result (first-two rest-of-list)) 
        (the-rest-after-two rest-of-list)))))))) 

(def get-the-file 
    (fn [file-name-and-path] 
    (let [the-file-pointer 
      (new java.io.RandomAccessFile (new java.io.File file-name-and-path) "r") 
     intermediate-array (byte-array (.length the-file-pointer))] ;reserve space for final length 
     (.readFully the-file-pointer intermediate-array) 
     (group-by-twos (seq intermediate-array))))) 

(get-the-file file-path) 

私が上で述べたように、私がたくさんの場所で駄目にすると、それは終わっていないようです。これを大容量ファイル用に実行するにはどうしたらいいですか?何が必要なのかを行うことの認知的な負担を取り除く手段がありますか?いくつかのルール?

+0

私は最終的にバイトではなく文字を読む必要があることに注意してください。あるいは、むしろ、私は最終的に各16ビットから符号付き数値を取得しようとしています。私はそれらのペアを取って、次のパスの間にマップで単一の数字に変えようとしていました。これを行うにはおそらくもっと良い方法があります... – MarkL4

+0

関連性のある徹底的な議論があります:(http://programming-puzzler.blogspot.com/2009/01/laziness-in-clojure-traps-workarounds.html) – MarkL4

+0

Clojureの豊富な組み込み関数とライブラリを使用して、コグニティブな負担を軽減する方法については、できるだけ独自のコードを書くようにしてください。 'group-by-twos'は本当に大きいですが、それほど効果はありません。また '(if(=(count a-list)2)true false)'は '(=(count a-list)2)'を冗長に表現する方法です。 –

答えて

2

ファイルを完全にメモリで読み込み、このバイト配列にseqを作成します。これは、必要なすべてのデータがすでにメモリにロードされているため、レイジーシーケンスの利点はありません/必要なときにデータを生成する。

(def get-the-file 
    (fn [file-name-and-path] 
    (let [the-file-pointer 
      (new java.io.RandomAccessFile (new java.io.File file-name-and-path) "r") 
     file-len (.length the-file-pointer)] ;get file len 
     (partition­ 2 (map (fn [_] (.readByte the-file-pointer)) (range file-len)))))) 

注:あなたが何ができるか

のようなものを使用してファイルの内容を超える配列を作成している私は実際にそれを試していないが、私はそれが怠惰なファイルについて、少なくともあなたのアイデアを与える願っています一部

2

を読んで私は、慣用的な解決策は以下のようになり推測:

(partition 2 (map int (slurp "/Users/me/Desktop/temp/bob.txt"))) 

完全なファイルがメモリにロードされているように、これは完全には怠け者ではありませんが、それがために問題なく動作するはずですあまりにも大きくないファイル。ただし、パーティションとマップは怠惰なので、バッファリングされたリーダーでslurpを置き換えると、完全に怠惰なバージョンになります。

注:これは、ファイルのサイズが奇数の場合、最後の文字を飲み込むことになります。サイズが奇妙な場合は、あなたが期待するものは明確ではありません。あなたが独自のリストの最後の値を持っているしたい場合は、(partition 2 2 [] ...)

user=> (partition 2 (map int "ABCDE")) 
((65 66) (67 68)) 
user=> (partition 2 2 [] (map int "ABCDE")) 
((65 66) (67 68) (69)) 
+0

ああ、パーティション、ありがとう。私は何かが組み込まれていることを知っていたはずです。実際のファイルはバイナリなので、私はスラップを使うことはできないと思いますか? – MarkL4

1

を使用することができ、大量のデータを扱うときにClojureのデータ構造に注意してください。 (典型的なClojureアプリケーションは、同じJavaアプリケーションよりも2〜3倍のメモリを使います - シーケンスはメモリが高価です)。 データ全体を配列に読み込むことができたら、それを行います。その後、処理中にガベージコレクションが確実に行われるように、シーケンスヘッドへの参照を保持しないようにしながら処理してください。

また、文字列はcharプリミティブよりもはるかに大きくなります。単一の文字列は26バイトで、charは2バイトです。 配列を使用したくない場合でも、arraylistは配列やベクトルより数倍小さいです。