web-dev-qa-db-ja.com

Python:辞書のメモリ使用量を減らす

いくつかのファイルをメモリにロードしようとしています。ファイルには、次の3つの形式のいずれかがあります。

  • 文字列TAB int
  • 文字列TABフロート
  • int TABフロート。

確かに、これが解決に役立つ場合のために、それらはngram静的ファイルです。例えば:

i_love TAB 10
love_you TAB 12

現在、私が今やっている擬似コードは

loadData(file):
     data = {}
     for line in file:
        first, second = line.split('\t')
        data[first] = int(second) #or float(second)

     return data

驚いたことに、ディスク内のファイルの合計サイズは約21 mbですが、メモリにロードされると、プロセスは120〜180 mbのメモリを消費します。 (全体pythonアプリケーションは他のデータをメモリにロードしません)。

10個未満のファイルがあり、それらのほとんどは、現在何百万行あるファイルを除いて、約50〜80k行で安定しています。

そこで、メモリ消費を削減するためのテクニック/データ構造を求めたいと思います。

  • 圧縮技術に関するアドバイスはありますか?
  • それでもdictを使用している場合、メモリを削減する方法はありますか? 「Java for Python dict?
  • 他のデータ構造がある場合、速度をいくらか犠牲にしてメモリを削減することもできます。それでも、これは時間に敏感なアプリケーションなので、ユーザーがクエリを入力すると、結果を返すのに数秒以上かかるのはあまり合理的ではないと思います。これに関して、私はまだGoogleがGoogle翻訳を非常に高速に処理する方法に驚いています:彼らは多くのテクニックと多くのサーバーのパワーを使用している必要がありますか?

どうもありがとうございました。あなたのアドバイスを楽しみにしています。

45
Paul Hoang

メモリフットプリントを改善するのに役立つ完全な戦略を提供することはできませんが、メモリを大量に消費しているものを正確に分析するのに役立つと考えています。

辞書のPython implementation(ハッシュテーブルの比較的単純な実装)、および組み込みの文字列と整数データ型の実装を見ると、たとえば here (具体的には、object.h、intobject.h、stringobject.h、dictobject.h、および../Objectsの対応する* .cファイル)、予想されるスペースをある程度の精度で計算できます。要件:

  1. 整数は固定サイズのオブジェクトです。つまり、参照カウント、タイプポインター、および実際の整数が含まれます。通常、32ビットシステムでは少なくとも12バイト24バイト 64ビットシステムでは、アライメントによって失われる可能性のある余分なスペースは考慮されません。

  2. stringオブジェクトは可変サイズです。つまり、

    • 参照カウント
    • タイプポインタ
    • サイズ情報
    • 遅延計算されたハッシュコード用のスペース
    • 状態情報(例:interned文字列に使用)
    • 動的コンテンツへのポインター

    合計で少なくとも24バイト32ビットまたは60バイト64ビットで、文字列自体のスペースを含まない。

  3. 辞書自体はいくつかのバケットで構成され、各バケットには

    • 現在保存されているオブジェクトのハッシュコード(使用される衝突解決戦略のためにバケットの位置から予測できない)
    • キーオブジェクトへのポインタ
    • 値オブジェクトへのポインタ

    合計で少なくとも12バイト32ビットで24バイト64ビットで。

  4. 辞書は8個の空のバケツで始まり、その容量に達するたびに2倍にリサイズされるエントリ数になります。

私はテストを実施しました32ビットマシンで、46,461の一意の文字列(連結文字列サイズ337,670バイト)のリストを使用して、それぞれ整数に関連付けられています-セットアップと同様です。上記の計算によれば、最小メモリフットプリントは

  • 46,461 *(24 + 12)バイト=文字列/整数の組み合わせで1.6 MB
  • 337,670 = 0.3 MBの文字列コンテンツ
  • 65,536 * 12バイト=ハッシュバケットの場合は1.6 MB(13回のサイズ変更後)

合計2.65 MB。 (64ビットシステムの場合、対応する計算は5.5 MBになります。)

Pythonインタープリターをアイドル状態で実行すると、ps- toolによるフットプリントは4.6 MBです。したがって、ディクショナリを作成した後に予想される合計メモリ消費量は約4.6 + 2.65 =7.25 MBです。私のテストでの真のメモリフットプリント(psによる)7.6 MBです。私は余分なCAを推測します。 Pythonのメモリ割り当て戦略(メモリアリーナなど)によって生成されたオーバーヘッドによって0.35 MBが消費されました

もちろん、多くの人々は、メモリフットプリントを測定するためのpsの使用が不正確であり、32ビットおよび64ビットシステムでのポインタタイプと整数のサイズに関する私の仮定が多くの特定で間違っている可能性があることを指摘するでしょうシステム。認めた。

しかし、それにもかかわらず、主要な結論、これらは次のように思われます:

  • Python辞書の実装驚くほどsmallのメモリを消費します
  • しかし、参照カウント、事前に計算されたハッシュコードなどのために、多くのintおよび(特に)文字列オブジェクトが占めるスペースは、最初に思った以上です
  • Pythonを使用し、文字列と整数を個々のオブジェクトとして表現したい限り、メモリオーバーヘッドを回避する方法はほとんどありません少なくともそれがどのように行われるかわかりません
  • (Pythonオブジェクトではなく)Cポインターとしてキーと値を保存するハッシュを実装するPython-C拡張を探す(または実装する)価値があるかもしれません。それが存在するかどうかはわかりません。しかし、それは実現でき、メモリフットプリントを半分以上削減できると信じています。
77
jogojapan

1)メモリ内のSQLiteは素晴らしいソリューションのように聞こえます。ロードされたデータをより簡単にクエリできるようになります。

sqlite3.connect( ':memory:')

2)おそらく名前付きのタプルが必要です-辞書よりも軽く、ドット表記を使用してプロパティにアクセスできると確信しています(とにかく美的好みがあります)。

http://docs.python.org/dev/library/collections

3)Redisをご覧ください: https://github.com/andymccurdy/redis-py

これは高速であり、物事を簡単に持続させることができます。つまり、使用するたびにセット全体をロードする必要はありません。

4)トライは良いアイデアのように聞こえますが、作業の終わりにいくつかの理論的な複雑さを追加します。ただし、Redisを使用して実装および保存することもできます。これにより、速度がさらに向上します。

しかし、全体として、名前付きタプルはおそらくここでのトリックです。

8
Moshe Bildner

ディスクには文字列だけがあり、Pythonにロードするとき、インタープリターは文字列自体に加えて、各文字列および各辞書の構造全体を作成する必要があります。

Dictによって使用されるメモリを削減する方法はありませんが、問題にアプローチする他の方法があります。メモリの速度と引き換えに喜んでいる場合は、メモリ内の辞書にすべてをロードするのではなく、SQLiteファイルの文字列を保存およびクエリすることを検討する必要があります。

6
Pedro Werneck

トライ( http://en.m.wikipedia.org/wiki/Trie )のようなデータ構造は、メモリ効率の向上に適しています。

更新:python dictのメモリ効率が問題として提起されましたが、サードパーティライブラリの可用性を考慮して標準ライブラリから拒否されました。参照: http:// bugs.python.org/issue952

4
Garrett

メモリオーバーヘッドなしでlog(n)アクセスのdictを blist.sorteddict に置き換えることができます。辞書とまったく同じように動作する、つまりすべてのメソッドを実装するため、初期型を変更するだけで済むので便利です。

3
ealfonso

数値データをコンパクトにpythonメモリに保存しようとする場合、おそらく最良の解決策はNumpyです。

Numpy( http://numpy.org )は、ネイティブC構造を使用してデータ構造を割り当てます。そのデータ構造のほとんどは、単一のデータ型を格納していることを前提としているため、これはすべての状況(nullなどを格納する必要があるかもしれません)ではありませんが、非常に、非常に、非常に高速で、ほぼ同じくらいコンパクトですあなたが求めることができます。多くの科学がそれで終わりました(SciPyも参照)。

もちろん、別のオプションがあります:zlib、以下がある場合:

  • 十分なCPUサイクル、および
  • メモリに収まらない大量のデータ

データの「ページ」(ただし、必要な大きさ)を配列として宣言し、データを読み取り、配列に格納し、Zipしてから、メモリにすべてのデータがあるまで、さらにデータを読み込むことができます。欲しいです。

次に、ページを繰り返し処理し、解凍し、配列に変換して、必要に応じて操作を行います。例えば:

def arrayToBlob(self, inArray):
    a = array.array('f', inArray)
    return a.tostring()

def blobToArray(self, blob, suppressWarning=False):
    try:
        out = array.array('f', [])
        out.fromstring(blob)
    except Exception, e:
        if not suppressWarning:
            msg = "Exception: blob2array, err: %s, in: %s" % (e, blob)
            self.log.warning(msg)
            raise Exception, msg
    return out

データをブロブとして取得したら、このブロブをzlibに渡し、データを圧縮できます。繰り返し値が多数ある場合、このblobは大幅に圧縮できます。

もちろん、すべてを非圧縮のままにするよりも時間がかかりますが、メモリに収まらない場合は、最初から選択が制限されます。

圧縮しても、すべてがメモリに収まらない場合があります。その時点で、圧縮されたページを文字列やピクルスなどとして書き出す必要があります。

がんばろう!

3
Kevin J. Rice