2016-08-02 2 views
2

クイックスタートガイド:Python 3.5では、メモリが約5GBと予想されるものの、代わりに15GBを超えてリソースが不足してクラッシュする。辞書のリストに名前付きタプルを追加すると膨大なメモリが肥大化する

import pickle 
from collections import namedtuple 

Hdr1 = namedtuple("Hdr1", "id hash source options elements locations") 
Hdr2 = namedtuple("Hdr2", "id hash source options stream locations") 
Hdr3 = namedtuple("Hdr3", "id hash source options series locations") 
Identifier = namedtuple("Identifier", "id hash") 
Location = namedtuple("Location", "index tell") 
IndexData = namedtuple("IndexData", "filenames packet1 packet2 packet3") 

filenames = [] # filled by other code, but it's a list, with 10 items 
packet1_d = {} 
packet2_d = {} 
packet3_d = {} 

index_data = IndexData(filenames, packet1_d, packet2_d, packet3_d) 

# for each file 
# read all the packets in the file, get the tell() location each time 
if packet is header: 
    if packet is packet1_header: 
    packet1_d[Identifier(id, hash)] = Hdr1(id, hash, source, options, []) 
    elif packet is packet2_header: 
    packet2_d[Identifier(id, hash)] = Hdr2(id, hash, source, options, stream, []) 
    else 
    packet3_d[Identifier(id, hash)] = Hdr3(id, hash, source, options, series, []) 
else 
    loc = Location(index, tell) 
    # This part below is deadly 
    if packet is packet1: 
    packet1_d[Identifier(id, hash)].locations.append(loc) 
    if packet is packet2: 
    packet2_d[Identifier(id, hash)].locations.append(loc) 
    if packet is packet3: 
    packet3_d[Identifier(id, hash)].locations.append(loc) 

pickle.dump(index_data, open("index_data.p", "wb")) 

詳細:これは明らかに、すべてのコードではありません - 私が開いて、ファイルを解析部品を保持し、あなたが問題を再現することはできませんので、明らかにあなたは、使用可能なファイルを持っていません。 。 isステートメントは擬似コードですが、論理的には同等です。これはデータ構造をどのように設定するのかを正しく表しているため、メモリ使用量の見積もりが正確で、変数の使用方法を正確に表しているため、メモリリークを検出するための代表的な方法です。

「致命的」とコメントした6行をコメントアウトすると、10 GBのデータと約100 Mのパケットを実行した後、ピケットファイル(ファイル名のみと異なるパケットヘッダーのリスト)がどこかにあります5〜10 MBです。私はpickle圧縮を知っていますが、それでも "ベースデータ"は50MB未満です。

合計91,116,480データパケットがあります。計算を簡単にするために、100Mと呼ぶだけです。各Locationは、ファイルのリストへの索引と、file.tell()からの戻り値です。

>>> import sys 
>>> from collections import namedtuple 
>>> Location = namedtuple("Location", "idx tell") 
>>> fobj = open("/really/big/data.file", "rb") 
>>> fobj.seek(1000000000) 
1000000000 
>>> tell = fobj.tell() 
>>> loc = Location(9, tell) 
>>> sys.getsizeof(loc) 
64 

だから、総メモリ使用量が6.4ギガバイト以下でなければならない:対話型シェルでの実証試験では、それぞれ「場所」は64バイトであると言います。

これはなぜ15 GB以上のメモリを占有するのですか?このデータを設定するためのメモリ効率的な方法がありますか?

私はこれをすべてのデータをsqliteデータベースファイルに入れていました。ファイル全体は2.1GBなので、生データは2.1GBを超えてはいけないようです。私は6GBの範囲でそれを得るPythonのオーバーヘッドを理解することができますが、それは15 GBを打つべきではありません。私はこの問題を回避することはできましたが、次回は回避する方法を知りたいと思います。

+0

'sys.getsizeof(' _tuple_オブジェクトのみのサイズであり、含まれているオブジェクトのサイズではありません)Pythonの各整数は約28バイト(64ビットビルド)の独立オブジェクトです。そしてピクルリング中に、プロセスはネイティブオブジェクトのインスタンスとシリアル化されたバージョンの両方をメモリに保持する必要があります(Pikcleは結果をファイルに書き込んでいるので、Pikcleはそれをしてはいけませんが、すでにエンコードされたオブジェクト循環参照を避けるため) – jsbueno

+0

タプル+ 2つのintが120バイトだと言っているのでしょうか?その100Mデータポイントは、そこの問題の多くを占めるでしょう。その場合、答えはタプルを使用しないことです。 Mathiasのような型付き配列の提案は、メモリフットプリントを大幅に減らすでしょうか? –

+0

私はまだsqliteを使っています。さもなければ、あなたの値を8バイトに保持する名前付きタプルではないカスタムクラスはいいでしょうが、あなたはまだオブジェクトのオーバーヘッドを持っています。 1、2、3の3要素配列にはかなりのオーバヘッドもあります.Nubmersを超えてオブジェクト情報を保持する必要があるからです。 – jsbueno

答えて

2

これは私が他の答えで言ったように、データをディスクに保存し、データベースシステム。

あなたが直面している問題は、コンパクトですが、名前付きタプルの各フィールド(数値のみを含むフィールドも含む)は完全なPythonオブジェクトです。そして、Pythonの整数は~30バイトを使用します - それは各フィールドに加えて名前付きタプルオブジェクトサイズ自体〜〜64バイトです。 standarlibraryで

、​​モジュールは、各レコードのみ、そのデータのために必要なバイト数が使用されたオブジェクト・レコードの配列を作成できることを「構造」の基本型があります。つまり、1つの4バイト整数と1つの8バイト整数を使用して構造体を作成すると、各レコードは12バイトになります。配列そのものについての情報は百バイトと数バイトです。 ctypes.Structure配列の問題は、固定サイズで作成しなければならないことです。最後にさらにレコードを追加することはできません。また、各レコードに対してスタンドアロンのStructureオブジェクトを1つ作成すると、レコードごとに〜100バイトのオーバーヘッドが発生します。

大きな数字を扱うためのPythonの事実上のライブラリと、Pandasの基礎となるエンジン(より高いレベルでのあなたの問題に対するより高い解決策かもしれない)は、指定されたレコードを持つ配列を作成し、各レコードについてしかし、普通の数値配列は同じサイズの問題を抱えています。配列に任意のレコードを追加することはできません。

パンダ - http://pandas.pydata.org/ - おそらくあなたがそこで使用する必要があります。

もしあなたがいないのであれば、私はPythonのstdlib "struct"を使ってメモリ内のデータを整列させる2つのクラスをまとめました。各12バイトのレコードはちょうど12バイトしか使用できません。それはpickleableです。

あなたはそれがあるとしてhttps://github.com/jsbueno/extralist/blob/master/extralist/structsequence.pyでファイルを使用することができます - ここhttps://docs.python.org/3/library/struct.html#format-strings あなたのコードに記載されているように、単にStructSequenceのインスタンスを使用し、各「StrutureSequence」オブジェクトは、多かれ少なかれnamedtupleように作成され、プラスレコードの構造情報リストを作成しているところでは、(フィールド互換の)名前付きタプルオブジェクトをこれらのシーケンスに追加することもできます。これらのオブジェクトはデータをメモリに保存するだけです。そして、ピクルスは彼らと一緒にうまくいくでしょう。

+0

ありがとうございます。これは、コードがどのように肥大化しているのかという最初の質問に答えて、問題を回避するための(カスタマイズされた!)ソリューションを私に提供します。 –

2

Pythonオブジェクトが500bの代わりに2Kbを使用するかどうかを知るのは難しいと思いますが、追跡している正確な問題を修正しても、データが2倍になるまで良いでしょう。もう一度サイズを変更してください。

必要なのは、データを読み込み/処理/書き込みするストリーミングアプローチに切り替えることです。これは、出力形式を変更することを意味します - それは "ピクルスファイル"でもかまいませんが、モノリシックな辞書の代わりに小さなオブジェクトをピックルすることができます(小さな辞書のシーケンスでさえ、読んでお互いに)。

しかし、出力データをsqliteデータベース(たとえあなたが必要なオブジェクトを列データとしてピックルすることができます)に切り替えると、このデータともっと多くのデータが得られます)

+0

に行くことができますありがとう。モノリスをより小さなビットに分割することを検討しましたが、その後、私がやっている検索のいくつかのためにそれらをすべてメモリにロードしなければなりません。データベースの考え方は私にとってはうまくいきます。私はsqlalchemyを調べなければならないでしょう。今、sqliteは私が必要とするすべてをやっているようです。 –

+0

実際には - それはtpoだった - 私はちょうどsqliteを書くつもりだったSqlalchemyはこれに対してbeoverkillかもしれない(それはdatabseではない - 複数のバックエンドを提供するORM) – jsbueno

+0

はい、 。更新していただきありがとうございます! –

2

locationsをオブジェクトリストの代わりにtyped arrayに変更してみてください。配列はメモリ内の効率的なCスタイルの配列として表されるため、N 32ビットの数値のリストにはN * 4バイトのメモリだけが必要です。

あなたLocationタイプは(簡潔にするため、示さのみpacket1場合)彼らは両方の32ビット整数をしている場合だけindextellは、そう、あなたがこのような'i'タイプコードを使用することができました。

import array 
LocationArray = namedtuple("LocationArray", "index tell") 
if packet is header: 
    locations = LocationArray(index=array.array('i'), tell=array.array('i')) 
    packet1_d[Identifier(id, hash)] = Hdr1(id, hash, source, options, locations) 
else: 
    loc = packet1_d[Identifier(id, hash)].locations 
    loc.index.append(index) 
    loc.tell.append(tell) 

(通常のタプルではなく、名前付きタプルLocationArrayを使用するように編集されています)

+0

型付き配列が私のレーダーからどのように落ちたのか分かりませんが、将来私はそれを覚えています。リストの私の使用のほとんどすべては、おそらく型付き配列として優れています。 –

関連する問題