わかりやすく解説:Pythonのメモリ管理の仕組みを詳しく解説

1. 記憶とは何ですか?

1.1. RAM の概要

随机存取存储器(Random Access Memory,RAM): データを一時的に保存するために使用されるコンピューター内のハードウェア コンポーネントです。これはコンピュータのメイン メモリの 1 つであり、オペレーティング システムが必要とする実行中のプログラムとデータを保存するために使用されます。

主な特徴:

  • 一時保管: RAM に保存されたデータは一時的なもので、コンピュータをシャットダウンまたは再起動すると消去されます。これは、データを長期間保持するハードドライブなどの永久記憶装置とは異なります。
  • ランダムアクセス: RAM にはランダム アクセス機能があるため、特定の順序で読み取らなくても、ストレージ内のあらゆるデータにすばやくアクセスできます。このため、RAM は、データにすばやくアクセスして処理するためのコンピュータの作業メモリとして使用するのに最適です。
  • 高速ストレージ: RAM は、データをミリ秒、場合によってはナノ秒で読み書きできる高速ストレージ デバイスです。これにより、コンピュータはタスクを迅速に実行できるようになり、パフォーマンスが向上します。
  • 小容量: RAM の容量は通常比較的小さく、通常はメガバイト (MB) またはギガバイト (GB) 単位で測定されます。コンピューターの RAM 容量は、マルチタスクや大規模なプログラムの実行能力に影響します。

1.2. RAM容量

RAM の容量: コンピュータが (現在/1 回) 同時にロードおよび計算できるデータとプログラムの合計メモリの上限を示します。例: 現在のコンピュータに 64 GB の RAM が搭載されている場合、同時にロードして実行できるデータとプログラムの総量は 64 GB を超えることはできません。そうしないと、(RAM) メモリが不足し、システムが非常に遅くなったり、システムがクラッシュしたりする可能性があります。

メモリ使用量:

  • オペレーティング システム: オペレーティング システムは、システムのコア機能を実行するために RAM の一部を必要とします。
  • アプリケーションの実行: 開いている各アプリケーションには、そのデータとコードを保存するために RAM の一部を割り当てる必要があります。大規模なアプリケーションやマルチタスクでは、より多くの RAM が必要になる場合があります。
  • 処理中のデータ: 開いているドキュメント、画像、ビデオ、またはオーディオ ファイルなど、すべてを保存するために RAM が必要です。
  • キャッシュと一時データ: オペレーティング システムとアプリケーションはデータの読み取りと書き込みを高速化するために RAM を使用することが多いため、一部の RAM はキャッシュと一時ストレージにも使用されます。
    RAM 容量を超えるデータやプログラムが読み込まれると、ハード ドライブや SSD などの永続ストレージ デバイスからデータを頻繁に読み取る必要があり、通常は速度が低下するため、コンピュータのパフォーマンスに影響します。パフォーマンスの低下。

1.3. コンピューターのメモリを確認する

ここに画像の説明を挿入します

1.4. コンピュータのメモリを監視する

現在のメモリ使用量
ここに画像の説明を挿入します

現在のメモリ使用率
ここに画像の説明を挿入します

2.RAM は CPU のメイン メモリであり、ビデオ メモリは GPU の専用メモリです

RAM(随机存取存储器)是中央处理器CPU的主内存: 実行中のプログラムやオペレーティング システムによって使用されるデータを一時的に保存するために使用されます。これは揮発性メモリの一種であり、コンピュータの電源を切るとそこに保存されているデータが失われます。

  • 機能: CPU は、コンピューターの実行中にさまざまなタスクをサポートするために、RAM を介してデータと命令を読み取ります。
  • パフォーマンス: RAM の速度と容量は CPU のパフォーマンスにとって重要です。より大容量で高速な RAM により、必要なデータがより効率的に配信され、CPU がデータの待機に費やす時間が短縮されます。

显存(Graphics RAM)是图形处理器GPU的专用内存: テクスチャ、フレーム バッファ、深度バッファなどのグラフィック データを保存するために使用されます。

  • 機能: ビデオ メモリは GPU 専用の高速メモリで、高速アクセスと高帯域幅を提供するように最適化されています。
  • パフォーマンス: ビデオ メモリのサイズと種類 (GDDR5、GDDR6 など) は、大規模なグラフィック データを処理する GPU の能力に直接影響します。ビデオ メモリの容量と帯域幅が大きいということは、通常、GPU がより複雑なグラフィックス タスクを処理できることを意味します。
  • CPU (Central Processing Unit): コンピュータ システムの中核であり、コンピュータの動作の制御、算術演算および論理演算の実行、システム リソースの管理などのシステム タスクの実行を担当します。 CPU のコア数は少なく (数個から数十個)、一般的なコンピューティングに適しています。
  • GPU (グラフィックス プロセッシング ユニット): 元々はグラフィックス レンダリング用に設計されましたが、その並列処理機能により科学技術コンピューティング、ディープ ラーニング、その他の分野でも広く使用されています。 GPU には多数の小さな処理コアがあり、大規模な並列コンピューティングに適しています。

共同作業: ディープ ラーニングでは、CPU がタスクの管理と作業のスケジュールを担当し、GPU は大規模な行列演算を加速し、トレーニング速度を向上させるために使用されます。

3. メモリ管理

3.0、さまざまなデータ型のメモリ範囲

コンピュータ メモリにおける画像メモリの計算式は次のとおりです。内存大小 = 宽度 × 高度 × 通道数 x 每个像素的字节数メモリの基本的な記憶単位はビットではなくバイトであり、整数で表されます。

# 在计算机中,最小的存储单元是位(bit),而一个字节(Byte)通常由8个位组成。

1 TB = 1024 GB = [1024^2] MB = [1024^3] KB = [1024^4] Bytes = [1024^4 x 8] bits
位(bits) + 字节(Bytes) + 千字节(Kilobytes,KB) + 兆字节(Megabytes,MB) + 吉字节(Gigabytes,GB) + 千兆字节(Terabytes,TB) + PB + EB + ZB + YB
データの種類 説明する ピクセルあたりのバイト数 データ範囲 メモリ範囲
ブール ブール型 1人 [0,1] 2bits
あなた8 符号付き 8 ビット整数 8ビット(1バイト) [-128、127] [-16,16]バイト
uint8 符号なし 8 ビット整数 8ビット(1バイト) [0,255] [0,32]Bytes
int16 符号付き 16 ビット整数 16ビット(2バイト) [-32768、32767] [-32,32]KB
uint16 符号なし 16 ビット整数 16ビット(2バイト) [0,65535] [0,64]KB
int32 符号付き 32 ビット整数 32ビット(4バイト) [-2,147,483,648,2,147,483,647] [-2,2]GB
uint32 符号なし 32 ビット整数 32ビット(4バイト) [0,4,294,967,295] [0,4]GB
int64 符号付き 64 ビット整数 64ビット(8バイト) [-9.22×1018,9.22×1018] [-8,8]EB
uint64 符号なし 64 ビット整数 64ビット(8バイト) [0,18,446,744,073,709,551,615] [0,16]EB

3.1、Python はどのようにメモリを割り当てますか?

Python メモリ マネージャーがメモリを割り当てる方法:オブジェクトのサイズに基づいて十分な大きさのメモリ ブロックを選択し、このメモリを 2 つの部分に分割します。1 つはオブジェクトのデータの保存用で、もう 1 つはオブジェクトの参照の保存用です。
(1) Python では、オブジェクトが使用されると自動的にメモリが割り当てられ、オブジェクトが使用されなくなるとメモリは自動的に解放されます。
(2) Python では、オブジェクトはすべて動的型であり (つまり、変数を宣言するときにデータ型を指定する必要はありません。Python が自動的に決定します)、それらはすべて動的メモリです。割り当て。

3.2、Python は自動メモリ管理メカニズムを採用

Python は、「参照カウント」と「循環参照検出」を通じてメモリ (つまり、ガベージ コレクター) を自動的に管理します。したがって、通常の状況では、プログラマはメモリの解放についてあまり注意を払う必要はありません。手動メモリ管理を考慮する必要があるのは、大規模なデータ セットを扱うときや、メモリをタイムリーに再利用する必要があるときなどの特殊な場合のみです。

自動メモリ管理メカニズム: ガベージ コレクター (gc) とも呼ばれます未使用のメモリとオブジェクトを定期的にスキャンして自動的にリサイクルするため、開発者はメモリ管理の問題を心配することなくプログラム ロジックに集中できます。
(1) ガベージ コレクター: 特定のリリース タイミングは、プログラマによって明示的に制御されるのではなく、インタプリタの内部戦略によって制御されます。
(2) ガベージ コレクター: 厳密に所定のサイクルに従って実行されるのではなく、世代アルゴリズムに従って収集します。

ガベージ コレクターのトリガー:

  • (1) 手動ガベージコレクション:gc.collect() を使用して手動でガベージ コレクションを強制し、メモリが敏感な場合 (メモリ不足など)、不要になったメモリの解放を加速する問題を解決します。
    • 1. Python は自動ガベージ コレクション メカニズムを使用して、参照されなくなったオブジェクトを自動的に検出し、バックグラウンドで定期的にメモリを解放します。 gc.collect() と比較すると、自動ガベージ コレクションには待機期間 (定期的な検出) が発生します。
    • 2. 通常の状況では、gc.collect() を手動で使用する必要はありません。Python のガベージ コレクション メカニズムは通常、十分に賢く、適切なタイミングで自動的に実行され、不要になったメモリを解放します。
    • 3、gc.collect() は、メモリをすぐに解放するのではなく、自動ガベージ コレクションの実行を高速化します (ただし、ほぼ同じです)。
    • 4、gc.collect() 自体も追加のオーバーヘッドを引き起こすため、手動でガベージ コレクションを頻繁にトリガーすることはお勧めできません。
  • (2) 参照カウント:ガベージ コレクション メカニズムは、各オブジェクトが他のオブジェクトによって参照された回数を記録します
    • 1. 参照カウントは 0 から始まります。
    • 2. オブジェクトにそれを指す新しい参照がある場合は、参照の数 +1。オブジェクトを指す参照が期限切れになると (del 对象 など)、その数は参照数 -1 。
    • 3、 オブジェクトの参照カウントが 0 の場合 (对象=None など)、オブジェクトはガベージ コレクション キューに入れられ、待機します。メモリをすぐに解放するのではなく、自動ガベージ コレクションのために。
      • [del オブジェクト]: del ステートメントを使用して名前空間からオブジェクトを削除すると、メモリはすぐに解放されますが、Python のメモリ プール メカニズムはメモリをコンピュータにすぐに解放せず、再利用されるのを待ちます。
      • [Object=None]: オブジェクトを None に設定すると、オブジェクトの参照カウントは 0 になりますが、すぐには解放されず、自動ガベージ コレクション メカニズムによってメモリが解放されるまで待機します。
  • (3) 循環基準検出:オブジェクト間に相互参照がある場合、オブジェクト間でリング構造が形成されるため、参照カウントがゼロにならないため、メモリを自動的に確保できません。リサイクルされ、メモリ リークが発生します。 世代のリサイクル
    • 1、引用链: オブジェクト間の参照関係を追跡するために使用されます。参照カウントがゼロではなく、オブジェクト間に循環参照が形成されている場合。
    • 2、分代回收(generation): は、すべてのオブジェクトを 0、1、2 の 3 つの世代に分割します。新しく作成されたオブジェクトはすべて世代 0 のオブジェクトであり、特定の世代のオブジェクトがガベージ コレクションを通過すると、次の世代にアップグレードされます。
      • 11. ガベージ コレクションが開始されると、すべての世代 0 オブジェクトがスキャンされます。
      • 22. ジェネレーション 0 が一定回数のガベージ コレクションを実行した場合は、ジェネレーション 0 と 1 をスキャンしてクリーンアップします。
      • 33. 世代 1 も一定数のガベージ コレクションを経験すると、0、1、2、つまりすべてのオブジェクトのスキャンを開始します。
  • (4) メモリプール: は、メモリ割り当てと小さなメモリ オブジェクトの解放効率を向上させるために使用されます。小さなメモリ オブジェクトの割り当てと解放を頻繁に行うと、メモリの断片化やパフォーマンスの低下につながる可能性があります。
    • 1、固定サイズのメモリ ブロックを事前に割り当てる: Python はオブジェクトのサイズに基づいて適切なメモリ ブロックを選択します。各メモリ ブロックには、同じサイズの複数の小さなブロックが含まれています。通常は 1 ブロックあたり 8 バイトです。
    • 2、メモリ ブロックのステータス: メモリ ブロックには、空き、割り当て、解放などのさまざまな状態があります。 Python はメモリ ブロックの状態を維持します。
    • 2、オブジェクトの再利用: メモリ ブロックに解放された小さなブロックが含まれている場合、Python はまずこれらの小さなブロックを再利用して、メモリ割り当てのオーバーヘッドを削減します。これは、毎回オペレーティング システムと対話する必要がなく、同じサイズのメモリ ブロックを複数回割り当てたり解放したりできることを意味します。
    • 3、遅延解放: 使用されなくなったメモリ ブロックはすぐにオペレーティング システムに解放されず、保持されます。将来の再利用のためのプール (オブジェクトの再利用)。
      优点: は、頻繁なメモリの割り当てと解放によって生じるパフォーマンスのオーバーヘッドを軽減します。
      缺点: は、使用されなくなったメモリ ブロックがオペレーティング システムによってすぐに再利用されないため、メモリ リークを引き起こす可能性があります。したがって、開発者はメモリ リークを避けるために、使用されなくなったオブジェクトへの参照を長期間保持しないようにする必要があります。

メモリ プールのメカニズム (ピラミッド モデルのカテゴリ): メモリ プールの図による理解
ここに画像の説明を挿入します

  • 第-1层,第-2层: OS 固有の仮想メモリ マネージャー (VMM) によって制御
    (レイヤー -1): カーネルの動的ストレージ割り当てと管理
    (階層) -2): 物理メモリ (ROM/RAM) + 二次ストレージ

    • ROM(只读存储器,Read-Only Memory): コンピュータまたはその他の電子デバイスのファームウェアおよび固定データを保存するために使用されるストレージ デバイス。通常、コンピュータのブート プログラム (BIOS) を保存するために使用されます。 RAM とは異なり、ROM 内のデータは通常変更できません。
    • RAM(随机访问存储器,Random Access Memory): 実行中のプログラムとデータを保存するための一時メモリ ストレージ デバイス。データを迅速に読み書きするためにコンピュータで一般的に使用されます。 RAM は揮発性であり、電源が切断されるとデータが失われます。
    • 二级存储(交换,Secondary Storage):ハードディスク ドライブ (HDD) やソリッド ステート ドライブ (SSD) などの不揮発性大容量ストレージ デバイスを指します。 。データ、ファイル、オペレーティング システムの長期保存に使用されます。 RAM とは異なり、二次ストレージ内のデータは電源が失われても失われません。
  • 第0层: メモリの割り当てとメモリの解放は、C 標準ライブラリの基盤となる汎用アロケータのmalloc と free によって実行されます。あ>

  • 第1层:要求されたメモリが 256 KB を超える場合、メモリ割り当ては Python のネイティブ メモリ アロケータ (生メモリ アロケータ) によって実行されます。基本的に、 C 標準ライブラリのmalloc や realloc などの関数を呼び出します。

  • 第2层:要求されたメモリが <256 KB の場合、メモリ割り当ては Python オブジェクト アロケータによって実行されます。

  • 第3层: ユーザーはオブジェクトの直接操作層を使用します。機能: Python 組み込みオブジェクトの場合 (int、dict、list、string など)、 各データ型はすべて独立したプライベート メモリ プールを持ち、オブジェクト間のメモリ プールは共有されません。たとえば、int によって解放されたメモリは float には割り当てられません。

3.3. Python の自動メモリ管理メカニズムの欠点

  • (1)メモリーリーク:プログラムはメモリを割り当てた後、通常、使用されなくなったメモリを解放できません。これにより、最終的にプログラムの速度が低下したり、クラッシュしたりする可能性があります。 一般的な状況としては、次のようなものがあります。
    • プログラムがメモリを割り当てても、不要になったときにメモリを解放しないと、メモリ リークが発生します。
    • データ構造が正しく設計されていない場合、オブジェクトが不要になったときにオブジェクトへの参照が保持され、メモリ リークが発生する可能性があります。
    • 循環参照や不正な参照カウントがある場合、メモリ リークが発生する可能性があります。
    • ファイルを開いて使用後に適切に閉じないと、メモリ リークが発生します。
  • (2)パフォーマンスの低下: メモリの割り当てと解放を同時に行う必要があるため、プログラムの速度が低下する可能性があります。
  • (3)ストレージが足りない注:プログラムが必要とするメモリがシステム空間のメモリより大きい場合、メモリ不足の問題が発生します。

3.4. Python のメモリ最適化方法

[Python 1 でメモリを最適化する方法] + [Python 2 でメモリを最適化する方法]

  • グローバル変数の使用量を減らす: グローバル変数はプログラムの終了まで存在するため、常にメモリを占有します。必要がない場合は可能な限りローカル変数を使用し、不要になったらすぐに解放してください。
  • 不要なオブジェクトの作成を避ける: Python では、オブジェクトの作成はメモリを割り当てる方法です。 したがって、不要なオブジェクトの作成を避け、オブジェクトを再利用してメモリ割り当ての数を減らすようにしてください。
  • 不要なオブジェクトを手動で解放する:gc.collect() を使用してガベージ コレクションを手動で強制します。これは、メモリが重要な場合 (メモリ不足など)、不要になったメモリをすぐに解放するために使用されます。

4. 実戦プロジェクト

4.1. オブジェクトの参照カウントを表示する

import sys

def create_objects():
    obj1 = [1, 2, 3]			    # 创建对象      (obj1对象的引用次数=2)
    obj2 = [obj1, 1]		        # 创建对象      (obj2对象的引用次数=1)
    obj3 = {
    
    'a': 1, 'b': 2}		    # 创建对象      (obj3对象的引用次数=1)
    print(sys.getrefcount(obj1))  # 获取对象a的引用次数
    print(sys.getrefcount(obj2))  # 获取对象a的引用次数
    print(sys.getrefcount(obj3))  # 获取对象a的引用次数
    #########################################################################
    obj1 = None  		# 将不再使用对象的引用设置为None     (obj2对象的引用次数=0)
    del obj2  		    # 将不再使用对象的引用设置为None     (obj1对象的引用次数=0)
    print(sys.getrefcount(obj1))  # 获取对象a的引用次数
    print(sys.getrefcount(obj3))  # 获取对象a的引用次数
    return

create_objects()  # 创建对象

"""###################################################################
# 函数:sys.getrefcount(a): 返回对象a的引用计数。
# 注意: 函数内部会增加一次临时引用计数来获取对象的引用数,但该函数执行之后会自动减去临时引用计数,以保持对象的引用计数不变。
# 
# 【del 对象】:	(1)使用del语句将对象从命名空间中删除,该对象的内存将被立即释放;
# 				(2)但Python的内存池机制导致该部分占用的内存不会立即释放给计算机,而是等待复用。
# 【对象=None】:	(1)将对象设置为None,该对象的引用计数变为零;
# 				(2)但内存不会立即释放,而是等待自动垃圾回收机制进行内存释放。
###################################################################"""

4.2. メモリプール: ガベージコレクションの i 番目の世代のしきい値を設定します

import gc
gc.set_threshold(700, 10, 5)

"""###################################################################
# 函数功能:设置垃圾回收的第i代阈值。
# 函数简介:gc.set_threshold(threshold0, threshold1, threshold2)
# 输入参数:    
#            threshold0      是垃圾回收的第0代阈值。当0代的垃圾数量达到这个值时,触发0代的垃圾回收。
#            threshold1      是垃圾回收的第1代阈值。当0代的垃圾数量达到 threshold0,且0和1两代垃圾的总数达到 threshold1,则触发两代的垃圾回收。
#            threshold2      是垃圾回收的第2代阈值。当0和1两代垃圾的总数达到 threshold1,且同时0/1/2三代垃圾的总数达到 threshold2,则触发三代的垃圾回收。
###################################################################"""

4.3、システム メモリの取得 + プロセスの取得 (実際のメモリ + ピーク メモリ)

def memory_usage():
    import psutil

    # (1)获取系统内存信息
    mem_info = psutil.virtual_memory()
    total_memory = mem_info.total / (1024 ** 3)  # 总内存大小(字节 - GB)
    used_memory = mem_info.used / (1024 ** 3)  # 已使用内存(字节 - GB)
    free_memory = mem_info.available / (1024 ** 3)  # 空闲的内存(字节 - GB)
    print(f"系统总内存RAM: {
      
      total_memory} GB")
    print(f"系统已占用内存: {
      
      used_memory} GB")
    print(f"系统未占用内存: {
      
      free_memory} GB")
    print("*" * 50)

    # (2)获取进程的内存信息
    process = psutil.Process()  # 创建一个进程对象
    mem_info = process.memory_info()  # 获取当前进程在RAM中的内存使用量
    memory_usage = mem_info.rss / (1024 ** 3)  # 表示进程在当前时刻的实际内存使用情况(字节 - GB)
    peak_memory = mem_info.peak_wset / (1024 ** 3)  # 表示进程在任意时间点的内存使用的峰值(字节 - GB)
    print(f"当前进程实际占用内存: {
      
      memory_usage_mb:.8f} GB")
    print(f"当前进程最大占用内存: {
      
      peak_memory_mb:.8f} GB")

    return memory_usage, peak_memory


if __name__ == "__main__":
    memory_usage()  # 创建对象

"""
系统总内存RAM: 63.74748229980469 GB
系统已占用内存: 8.997417449951172 GB
系统未占用内存: 54.750064849853516 GB
**************************************************
当前进程实际占用内存: 0.01511765 GB
当前进程最大占用内存: 0.01512146 GB
"""

4.4、メモリを手動で解放する

"""########################################################################
# 函数: gc.collect(): 手动垃圾回收管理
# 功能:
#     若使用gc.collect():	(1)不能保证立即释放内存,但可以加速自动垃圾回收的执行速度。
#                           (2)其本身也会引起额外的开销,不建议频繁触发手动垃圾回收。
#     若不使用gc.collect():	垃圾回收器是自动定期检测并回收内存,但有一定延迟(定期)。
########################################################################
# 【del 对象】:	(1)使用del语句将对象从命名空间中删除,该对象的内存将被立即释放;
# 				(2)但Python的内存池机制导致该部分占用的内存不会立即释放给计算机,而是等待复用。
# 【对象=None】:	(1)将对象设置为None,该对象的引用计数变为零;
# 				(2)但内存不会立即释放,而是等待自动垃圾回收机制进行内存释放。
########################################################################"""
import gc
import numpy as np


def memory_usage():
    """用于获取当前程序的内存占用情况(单位:MB)"""
    import psutil
    process = psutil.Process()  # 创建一个进程对象,用于获取当前程序的内存信息。
    mem_info = process.memory_info()  # 获取当前进程在RAM中的内存使用量
    memory_usage = mem_info.rss / (1024 ** 2)  # 表示进程在当前时刻的实际内存使用情况(字节 - MB)
    peak_memory = mem_info.peak_wset / (1024 ** 2)  # 表示进程在任意时间点的内存使用的峰值(字节 - MB)
    return memory_usage, peak_memory
    # 1 TB = 1024 GB = [1024^2] MB = [1024^3] KB = [1024^4] Bytes = [1024^4 x 8] bits


if __name__ == "__main__":
    # (1)查看系统初始的内存使用情况
    system_memory, peak_memory = memory_usage()
    print(f"系统初始的内存使用情况(MB): {
      
      system_memory:.2f}")
    ######################################################
    # (2)创建一个数组
    array = np.random.randint(0, 100, size=(400, 500, 600))
    print(f"总内存使用情况(MB): {
      
      memory_usage()[0]:.2f}, {
      
      memory_usage()[1]:.2f}")
    ######################################################
    # (3)查看系统进程的内存使用情况
    array[array <= 2800] = 0  # 灰度强度滤波
    print(f"总内存使用情况(MB): {
      
      memory_usage()[0]:.2f}, {
      
      memory_usage()[1]:.2f}")
    ######################################################
    # (4)查看(手动垃圾回收)系统进程的内存使用情况
    array = None
    gc.collect()  # 手动垃圾回收:加速自动垃圾回收
    print(f"总内存使用情况(MB): {
      
      memory_usage()[0]:.2f}, {
      
      memory_usage()[1]:.2f}")

    """
    系统初始的内存使用情况(MB): 29.73
    总内存使用情况(MB): 487.60, 487.61
    总内存使用情况(MB): 487.61, 602.06
    总内存使用情况(MB): 29.85, 602.06
    """

おすすめ

転載: blog.csdn.net/shinuone/article/details/133982613