bugfix> python > 投稿

メモリ使用量テストを複製しようとしていましたここに。

基本的に、この投稿は次のコードスニペットを与えたと主張しています。

import copy
import memory_profiler
@profile
def function():
    x = list(range(1000000))  # allocate a big list
    y = copy.deepcopy(x)
    del x
    return y
if __name__ == "__main__":
    function()

呼び出す

python -m memory_profiler memory-profile-me.py

64ビットコンピューターでの印刷

Filename: memory-profile-me.py
Line #    Mem usage    Increment   Line Contents
================================================
 4                             @profile
 5      9.11 MB      0.00 MB   def function():
 6     40.05 MB     30.94 MB       x = list(range(1000000)) # allocate a big list
 7     89.73 MB     49.68 MB       y = copy.deepcopy(x)
 8     82.10 MB     -7.63 MB       del x
 9     82.10 MB      0.00 MB       return y

同じコードをコピーして貼り付けましたが、プロファイラーは

Line #    Mem usage    Increment   Line Contents
================================================
 3   44.711 MiB   44.711 MiB   @profile
 4                             def function():
 5   83.309 MiB   38.598 MiB       x = list(range(1000000))  # allocate a big list
 6   90.793 MiB    7.484 MiB       y = copy.deepcopy(x)
 7   90.793 MiB    0.000 MiB       del x
 8   90.793 MiB    0.000 MiB       return y

この投稿は古くなっている可能性があります---プロファイラーパッケージまたはpythonが変更されている可能性があります。いずれにせよ、私の質問は、Python 3.6.x

(1) copy.deepcopy(x) は(上記のコードで定義されているように)自明ではない量のメモリを消費しますか?

(2)なぜ複製できなかったのですか?

(3) x = list(range(1000000)) を繰り返した場合 del x の後 、最初に x = list(range(1000000)) を割り当てたのと同じ量だけメモリが増加しますか(私のコードの5行目のように)?

回答 1 件
  • copy.deepcopy()  再帰的にコピーする可変オブジェクトのみ、整数や文字列などの不変オブジェクトはコピーされません。コピーされるリストは不変の整数で構成されているため、 y  コピーは同じ整数値への参照を共有することになります:

    >>> import copy
    >>> x = list(range(1000000))
    >>> y = copy.deepcopy(x)
    >>> x[-1] is y[-1]
    True
    >>> all(xv is yv for xv, yv in zip(x, y))
    True
    
    

    そのため、コピーでは、100万の参照を持つ新しいリストオブジェクトを作成するだけで済みます。これは、Mac OS X 10.13(64ビットOS)で作成したPython 3.6で8MBを少し超えるメモリを消費するオブジェクトです。

    >>> import sys
    >>> sys.getsizeof(y)
    8697464
    >>> sys.getsizeof(y) / 2 ** 20   # Mb
    8.294548034667969
    
    

    空の list  オブジェクトは64バイトを使用し、各参照は8バイトを使用します。

    >>> sys.getsizeof([])
    64
    >>> sys.getsizeof([None])
    72
    
    

    Pythonリストオブジェクトは全体的にスペースを増やして range() を変換します  リストのオブジェクトは、 deepcopy を使用する場合よりも追加の成長のために少し多くのスペースを作成します 、だから x  まだサイズが少し大きく、再度サイズ変更する前に125k個のオブジェクトを追加するスペースがあります。

    >>> sys.getsizeof(x)
    9000112
    >>> sys.getsizeof(x) / 2 ** 20
    8.583175659179688
    >>> ((sys.getsizeof(x) - 64) // 8) - 10**6
    125006
    
    

    一方、コピーには約87k分の追加スペースしかありません。

    >>> ((sys.getsizeof(y) - 64) // 8) - 10**6
    87175
    

    Python 3.6では、記事の主張を再現することはできません。これは、Pythonが多くのメモリ管理の改善を見たためと、記事がいくつかの点で間違っているためです。

    copy.deepcopy() の動作  リストと整数に関しては決して  copy.deepcopy() の長い歴史の中で変更された  (1995年に追加されたモジュールの最初のリビジョンを参照してください)、メモリ数値の解釈は、Python 2.7でも間違っています。

    具体的には、私はできる Python 2.7を使用して結果を再現するこれは私のマシンで見たものです:

    $ python -V
    Python 2.7.15
    $ python -m memory_profiler memtest.py
    Filename: memtest.py
    Line #    Mem usage    Increment   Line Contents
    ================================================
         4   28.406 MiB   28.406 MiB   @profile
         5                             def function():
         6   67.121 MiB   38.715 MiB       x = list(range(1000000))  # allocate a big list
         7  159.918 MiB   92.797 MiB       y = copy.deepcopy(x)
         8  159.918 MiB    0.000 MiB       del x
         9  159.918 MiB    0.000 MiB       return y
    
    

    起こっているのは、Pythonのメモリ管理システムが追加の拡張のために新しいメモリチャンクを割り当てていることです。それは新しい y ではありません  リストオブジェクトは約93MiBのメモリを使用します。これは、OSがPythonのプロセスにオブジェクトヒープ用のメモリを追加要求したときに割り当てた追加メモリにすぎません。リストオブジェクト自体はたくさん 小さい。

    Python 3 tracemalloc  モジュールは実際に何が起こるかについてはるかに正確です:

    python3 -m memory_profiler --backend tracemalloc memtest.py
    Filename: memtest.py
    Line #    Mem usage    Increment   Line Contents
    ================================================
         4    0.001 MiB    0.001 MiB   @profile
         5                             def function():
         6   35.280 MiB   35.279 MiB       x = list(range(1000000))  # allocate a big list
         7   35.281 MiB    0.001 MiB       y = copy.deepcopy(x)
         8   26.698 MiB   -8.583 MiB       del x
         9   26.698 MiB    0.000 MiB       return y
    
    

    Python 3.xのメモリマネージャーとリストの実装は、2.7のものよりもスマートです。明らかに、新しいリストオブジェクトは、 x の作成時に事前に割り当てられた既存の利用可能なメモリに収まることができた 。

    手動でビルドされたPython 2.7.12 tracemallocバイナリと memory_profile.py への小さなパッチを使用して、Python 2.7の動作をテストできます。 。これで、Python 2.7でもより安心できる結果が得られます。

    Filename: memtest.py
    Line #    Mem usage    Increment   Line Contents
    ================================================
         4    0.099 MiB    0.099 MiB   @profile
         5                             def function():
         6   31.734 MiB   31.635 MiB       x = list(range(1000000))  # allocate a big list
         7   31.726 MiB   -0.008 MiB       y = copy.deepcopy(x)
         8   23.143 MiB   -8.583 MiB       del x
         9   23.141 MiB   -0.002 MiB       return y
    
    

    著者も混乱していたことに注意してください。

    copy.deepcopy  両方のリストをコピーし、再び〜50 MB(50 MB-31 MB = 19 MBの追加オーバーヘッドがどこから来るのかわかりません

    (大胆な強調鉱山)。

    ここでのエラーは、Pythonプロセスサイズのすべてのメモリ変更が特定のオブジェクトに直接起因すると想定することですが、メモリマネージャが(削除します!)メモリー「アリーナ」、必要に応じてヒープ用に予約されたメモリーのブロック。それが理にかなっている場合は、より大きなブロックでそうします。 PythonのマネージャーとOS malloc の間の相互作用に依存するため、ここでのプロセスは複雑です。  実装の詳細。著者は、Pythonのモデルに関する最新の記事を発見しました。これは、最新のものであると誤解されているものです。その記事の著者自身が、すでにこれを指摘しようとしています。 Python 2.5では、Pythonがメモリを解放しないという主張はもはや真実ではありません。

    厄介なのは、同じ誤解が作成者を pickle の使用に対して推奨するように導くことです 、しかし実際には、Python 2でさえモジュールは再帰的な構造を追跡するために少しの記帳メモリ以上を決して追加しません。私のテスト方法論については、この要点をご覧ください。 cPickle を使用する  Python 2.7では、46MiBの1回限りの増加が追加されます( create_file() を2倍に  呼び出しの結果、メモリはそれ以上増加しません)。 Python 3では、メモリの変更は完全になくなりました。

    Theanoチームと投稿についてダイアログを開きます。記事は間違っており、混乱を招きます。Python2.7はまもなく完全に廃止されるため、Python 3のメモリモデルに集中する必要があります。(*)

    作成するとき新しいリスト  range() から 、コピーではなく、 x を作成する場合と同様にメモリが増加します  これは、新しいリストオブジェクトに加えて、新しい整数オブジェクトのセットを作成するためです。特定の小さな整数のセットは別として、Pythonは range() の整数値をキャッシュして再利用しません  操作。


    (*) 補遺:Thanoプロジェクトで課題#6619を開きました。プロジェクトは私の評価に同意し、ドキュメントからページを削除しましたが、公開バージョンはまだ更新されていません。

あなたの答え