プログラミングでは、メモリを割り当て、その後解放する必要があります。C言語でプログラムを書く場合、malloc/free関数に慣れ親しむでしょう。残念ながら、これによりメモリリークの脆弱性が生じます:回収されないメモリです。今日の人気のあるプログラミング言語のほとんどは、自動メモリ管理を使用しています:Java、JavaScript、Python、C#、Go、Swiftなどです。

自動メモリ管理には本質的に2つのタイプがあります。最も単純な方法は参照カウントです。各オブジェクトへの参照の数を追跡します。オブジェクトへの参照がなくなると、そのオブジェクトに関連付けられたメモリを解放できます。SwiftとPythonは参照カウントを使用します。参照カウントの欠点は循環参照です。メインプログラムがオブジェクトAを参照し、次にオブジェクトBを追加してオブジェクトAを参照させ、オブジェクトAもオブジェクトBを参照するようにすることができます。したがって、オブジェクトBには1つの参照があり、オブジェクトAには2つの参照があります。メインプログラムがオブジェクトAへの参照を破棄した場合、オブジェクトAとBの両方の参照カウントはまだ1のままです。しかし、それらは解放されるべきです。この問題を解決するには、到達不能なオブジェクトを検出するためにすべてのオブジェクトにアクセスする必要がありますが、AとBを含めてそれには時間がかかります。したがって、別の一般的な自動メモリ管理のアプローチ:世代別ガベージコレクションです。ほとんどのメモリは割り当て直後に解放されるという事実を利用します。したがって、若いオブジェクトを追跡し、時々それらにアクセスします。そして、より稀に完全スキャンを行います。世代別ガベージコレクションの欠点は、一般的な実装ではメモリスキャンのためにプログラムを停止させることです。多くの場合、プログラム全体が停止します。数十年にわたる研究により、実装には多くのバリエーションがあります。

一般的なPython実装には両方のタイプがあります:参照カウントと世代別ガベージコレクションです。世代別ガベージコレクションコンポーネントは一時停止を引き起こす可能性があります。多くのサーバーがPythonで書かれています。つまり、サービスが一時的に利用できなくなる可能性があります。これらはしばしば「stop the world」一時停止と呼ばれます。この一時停止はどれくらい長くなるのでしょうか?

これをテストするために、私は古典的なリンクリストを作成するPython関数を書きました:

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None
    def add_next(self, node):
        self.next = node

def create_linked_list(limit):
    """ create a linked list of length 'limit' """
    head = Node(0)
    current = head
    for i in range(1, limit):
        new_node = Node(i)
        current.add_next(new_node)
        current = new_node
    return head

次に、1つの大きなリンクリストを作成し、その後、タイトなループで即座に破棄される小さなリンクリストを作成します。

x = create_linked_list(50_000_000)
for i in range(1000000):
    create_linked_list(1000)

私のコードの主要な特徴は、5000万のリンクリストです。これはプログラムの最後まで解放されませんが、ガベージコレクタはまだそれを調べる可能性があります。

そして、ループ内の2回の反復間の最大遅延を記録します(time.time()を使用)。

どれほど悪化する可能性があるでしょうか?答えはPythonのバージョンによって異なります。また、実行ごとに一貫性がありません。そのため、私は1回実行して得られた結果を選びました。遅延をミリ秒で表します。

python version system max delay
3.14 macOS (Apple M4) 320 ms
3.12 Linux (Intel Ice Lake)  2,200 ms

この遅延のほとんどすべて(320 ms程度)はガベージコレクションによるものです。1000要素のリンクリストを作成するには1ミリ秒未満しかかかりません。

320 msはどれくらいの長さでしょうか?3分の1秒なので、人間が気づくのに十分な長さです。参考までに、画面を1秒間に60回描画するビデオゲームは画面描画に17 ms未満しかありません。2,200 msの遅延は、ユーザーの視点からはサーバークラッシュのように見え、確実にタイムアウト(リクエスト失敗)を引き起こす可能性があります。

私はPythonプログラムをGoに移植しました。同じアルゴリズムですが、直接比較は不公平かもしれません。それでも、参考値として役立ちます。

go version system max delay
1.25 macOS (Apple M4) 50 ms
1.25 Linux (Intel Ice Lake)  33 ms

したがって、Goの一時停止はPythonよりも数倍短く、壊滅的な2秒の一時停止はありません。

これらの一時停止は懸念すべきでしょうか?ほとんどのPythonプログラムは同時にメモリ内にこれほど多くのオブジェクトを作成しません。したがって、シンプルなWebアプリやスクリプトを使用している場合、これらの長い一時停止を見る可能性は低いでしょう。Pythonにはgc.set_thresholdgc.freezeなどのオプションがいくつかあり、動作を調整するのに役立ちます。

ソースコードはこちらから入手できます

Video