1

大きなデータ セット (最大 250k 行) を読み取って大きなセットを構築しています。効率を高めるために、セット内包表記を使用してセットを構築することにしましたが、メモリ エラーが発生し続けています。これは理にかなっています。集合内包表記の評価中にガベージ コレクターが機能しないと考えています。実際のソースコードは以下です。

def parsedData(filePath):
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        allJobs = {Job(rawJob(row)) for row in reader}
    return allJobs

ガベージ コレクタがメモリ エラーに達しようとしているときに、ガベージ コレクタを強制的にクリアする方法はありますか? これを行うより速い方法はありますか?セット内包表記の代わりにラムダ関数を使用すると、同じメモリの問題が発生しますか?

4

4 に答える 4

1

これが発生する理由は次のとおりです。

カスタム オブジェクトの一意のセットを作成するには、オブジェクトに少なくとも と を実装する必要があり__eq__ます__hash__

デモ:

class Obj(object):
    def __init__(self,val):
        self.val=val

    def __repr__(self):
        return self.val

li=['i am uniq','i am uniq','i am uniq','not really','not really','not really']        
print set(Obj(e) for e in li)

版画

set([i am uniq, i am uniq, not really, not really, i am uniq, not really])

__eq__必要な__hash__メソッドを追加します。

class Obj(object):
    def __init__(self,val):
        self.val=val
        self.hash=hash(val)

    def __repr__(self):
        return self.val

    def __eq__(self,other):
        return self.hash==other.hash    

    def __hash__(self):
        return self.hash        

li=['i am uniq','i am uniq','i am uniq','really','really','really']        
print set(Obj(e) for e in li) 

版画:

set([i am uniq, really])

適切なハッシュと eq がないと、セットがまったくないことがわかります。Jobsobj idに基づく順序付けられていないリストが作成されます。つまり、Python にとっては、「同じ」と定義したオブジェクトであっても、それぞれが異なる obj id を持っているため、異なるオブジェクトとして扱われます。この「セット」は、実際には同等のリストよりもかなり大きくなります。


それはさておき-これを行うためのよりメモリ効率の良い方法は、おそらくジェネレーターでnumpyの構造化配列を使用して、ファイルを行ごとに読み取ることです。

ストラテジー:

  1. ファイルを開く
  2. ファイルの合計行数を決定します。これは、ファイル内の最悪の場合の合計レコード数です。
  3. ファイルを巻き戻す
  4. 最初のレコードを読み取り、行データ (int、float、bytes など) のレコードに基づいて、numpy 配列の最も効率的なレコード構造を決定します。これは、Python オブジェクトの場合よりもはるかに高密度です。
  5. ファイルを巻き戻す
  6. データを一意化するためにハッシュが必要な場合は、それをレコードに追加します
  7. numpy配列をlines X recordsサイズに事前に割り当てます
  8. ファイルを 1 行ずつ読み取り、各レコードを numpy 配列に配置するジェネレータを作成します。
  9. 重複するレコードを追加しないでください (ハッシュに基づく)
  10. 追加されていない重複レコードの総数の numpy 配列のサイズを変更します...
于 2013-03-02T15:53:32.833 に答える
1

あなたは言う:

元のファイルに重複があってはなりません。セットのキャストは純粋に反復を高速化するためのものです。

マーフィーの法則によると、重複が発生します。「反復を高速化する」は、時期尚早で疑わしい最適化のように思えます。

いくつかのトリアージの提案:

(0) 少数の行でテストして、適切に機能していることを確認してください (特に、ジョブ オブジェクトがハッシュ可能であり、ハッシュ メソッドが一意性の定義を真に反映していることを確認してください)。

(1) シンプルに保ちます。つまり、集合内包表記やジェネレーターなどを避けます。古き良き方法で 1 つずつ実行します。理解などはエレガントですが、何も問題が発生しないユースケースに制限されています...ハーフギグのテキストファイルを処理しています。

(2) デバッグ コードを挿入して、メモリ不足になる前にどこまで進んでいるかを確認します。

(3) 重複チェック!

def parsedData(filePath):
    allJobs = set()
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        for rownum, row in enumerate(reader, start=1):
            job = Job(rawJob(row))
            if job in allJobs:
                pass # add code to display dupe
            else:
                allJobs.add(job)
            if rownum % 10000 == 0:
                print rownum, "rows; ", len(allJobs), "unique"
    return allJobs
于 2013-03-03T00:43:57.343 に答える
0

すべての行が一意であり、セットの代わりにリスト内包表記を使用しても MemoryError が発生する場合は、関数をジェネレーターにして、すべてのジョブを一度にロードしないようにすることができます。

def get_jobs(filePath):
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        for row in reader:
            yield Job(rawJob(row))

次のように使用できます。

for job in get_jobs(path):
    process(job)
于 2013-03-02T12:26:35.180 に答える
0

アップデート:

@Hyperboreus が指摘したように、これはValueError: I/O operation on closed file. 役に立つ場合に備えて、ここに残します。この問題の最善の解決策は、おそらくファイルを開いたままにし、ジェネレーターを使用して最後の最後にのみデータを読み取ることだと思います。


これを試して:

def parsedData(filePath):
    with open(filePath, 'rb') as rawData:
        reader = csv.reader(rawData, delimiter=",")
        allJobs = (Job(rawJob(row)) for row in reader)
    return allJobs

集合内包表記の代わりにジェネレーター式を使用して、ジェネレーターを作成して返します。

python wikiから:

ジェネレーターの使用によるパフォーマンスの向上は、値の遅延 (オンデマンド) 生成の結果であり、メモリ使用量の削減につながります。さらに、使用を開始する前に、すべての要素が生成されるまで待つ必要はありません。

于 2013-03-02T07:20:56.140 に答える