第3回の言語サミットでの講演は、マーク・シャノン氏によるものでした。彼は、Python 3.14に搭載されたインクリメンタル・ガベージコレクタの実装者です。しかし、本実装は、実稼働環境で「著しいメモリ負荷」が発生するという報告を受け、Python 3.13では世代別ガベージコレクタに戻されました。この新しいインクリメンタル・ガベージコレクタの当初の目的は、より大きなメモリ領域において、最大停止時間を桁違いに短縮することでした。

マーク氏の講演は、Pythonが時間をどのように費やしているかを示すグラフから始まりました。その内訳は、インタプリタ、参照、モジュール、そして今回の講演の焦点であるガベージコレクションです。ガベージコレクションは、実行時間の約11.67%を占めています。マーク氏は、Just-in-Time (JIT) コンパイラによってインタプリタが高速化されたとしても(実行時間の30.6%を占めます)、残念ながら、実行時間をさらに高速化するためには、依然としてメモリ管理とガベージコレクションについて考慮する必要がある、と述べています。マーク氏の講演は、これらのタスクにかかる時間を最小限に抑えることに焦点を当てていました。

今日のPythonのガベージコレクタは、具体的にどのような処理を行っているのでしょうか? 興味深いことに、この約12%の時間は、Pythonの主要なガベージコレクションの仕組みではありません。主要な仕組みは、参照カウントです。ガベージコレクタは、「バックアップ」であり、「もっと高速であるべき」です。参照カウントはすでに不要なオブジェクトを回収しているため、ガベージコレクタは、不要で到達不能なサイクルを見つけるだけで済むはずです。

マーク氏は、ガベージコレクション中の停止時間を強調しました。これは、インクリメンタル・ガベージコレクタが改善しようとした重要な指標であり、特にサーバーやユーザーインターフェースを持つアプリケーションにとって大きな問題でした。インクリメンタル・ガベージコレクタは、ガベージコレクション中のピーク停止時間を、「数十ミリ秒」に短縮しました。これは、現在の世代別ガベージコレクタの「約3秒」から大幅な改善です。

では、ガベージコレクタが改善されたかどうかをどのように判断するのでしょうか? マーク氏は、「有効性」という用語を定義し、ガベージコレクションのさまざまなアプローチについて考える上で役立つ他の定義も提示しました。

Scavenge(ガベージ収集):これは、最も基本的なガベージコレクタの動作です。ガベージコレクタにオブジェクトのセットを与えると、到達不能なサイクルにあるオブジェクトが収集されます。高レベルでは、ユーザーはガベージコレクタがこのタスクをどのように実行するかを気にする必要はありません。

Scavengeの効果:これは最適化される指標であり、「収集されたオブジェクト数」を「訪問されたオブジェクト数」で割った値で計算されます。Pythonの2つのガベージコレクタの効果は「非常に低い」状態で、世代別GCは0.3%、リバートされたインクリメンタルGCは約1%です。

世代仮説:これは、「ほとんどのオブジェクトは短命である」という仮定であり、ガベージコレクタはこれを利用してより効果的に動作することができます。ただし、正確な動作は実行されるプログラムによって異なります。

スペース:連続的に割り当てられたオブジェクトのグループです。すべての新しいオブジェクトは、最も新しい「スペース」に追加されます。スペースには定義されたサイズがあり、満杯になると、GCはスペースのガベージ収集を開始し、新しい空のスペースが作成され、そこに新しく作成されたオブジェクトが追加されます。スペースが新しいオブジェクトの追加を停止する期間は、ガベージ収集を行うのに適した機会です。なぜなら、開いているスペースではガベージ収集の操作は行われないからです。

世代:連続したスペースのグループです。世代はスペースよりもはるかに少なく、通常はガベージコレクタに組み込まれています。スペースは、世代を通じて常に同じ方向に移動します。

Hood Chathamは、Pythonのガベージコレクタと比較して、他のガベージコレクタの「効果」について疑問に思っていました。Markは、他のプログラミング言語のガベージコレクタとPythonを直接比較することは「公平ではない」と指摘しました。その理由は、「他のどの言語も参照カウントとガベージ収集の両方を行っていない」からです。

マークは次に、現在は以前のバージョンに戻された、段階的なガベージコレクタについて説明しました。このガベージコレクタは、世代別のコレクタではなく、すべてのヒープを含む単一の世代のみを持つものでした。これにより、段階的なガベージコレクタは「効果的」になりました。なぜなら、存在期間が長い領域には、より多くのオブジェクトが収集される傾向があるからです。しかし、世代別のGCは、メモリ使用量に悪影響を与えるというトレードオフがあります。

両方の利点を組み合わせることは可能でしょうか?

世代別のガベージコレクタは、全体的にメモリ使用量が少ないものの、一時停止時間が長くなるという欠点があります。一方、段階的なガベージコレクタは、一時停止時間は短いものの、より多くのメモリを消費します。Pythonは、デフォルトとしてどちらを採用すべきでしょうか?

マークの最終的な提案では、世代別と段階的なガベージコレクタの概念を組み合わせることで、両方の利点を実現する方法が示されていました。

具体的には、以下のようになります。

* 2つの世代:1つは「若い」世代、もう1つは「古い」世代。
* 若い世代と古い世代の段階的な収集器を交互に使用する。
* 「若い」世代の初期サイズを20MBに固定する。
* 古い世代の生存率は、若い世代の生存率の2倍で収集する。

ドンヒ・ナ氏は、ガベージコレクタの変更が誰かに問題を引き起こす可能性があることを懸念していました。ドンヒ氏は、異なるガベージコレクタ間の移行期間を設ける方法があるかどうか疑問に思いました。マークは、現在のガベージコレクタに「問題」があるわけではなく、主な問題は「保守する必要があるコードが増える」ことであると答えました。しかし、ユーザーがGCを自分で調整しない限り、デフォルトでより優れたGCを提供することを希望します。

ドンヒ氏は、JVMのガベージコレクタで利用可能な設定オプションと同様のものを、ユーザーがニーズに合わせて設定を調整できるように提供できるかどうか尋ねました。マークは、それが必須ではないと考えました。JVMの言語では、GCが「全て」ですが、PythonではGCはあくまで参照カウントの「バックアップ」に過ぎません。

ユッカ・レトサロ氏は、マーク氏の活動を支援したいという意向を示し、GCを最適化した既存のアプリケーションについて質問しました。彼は、これらの最適化は「GCが変更されるたびに古くなる」と指摘し、たとえ新しいGCの方が平均的に優れている場合でも、その影響を受ける可能性があることを示唆しました。マーク氏は、適切に行えば、PythonがGCの最適化の必要性をなくすことができるのではないかと期待しています。

グレゴリー・P・スミス氏は、ガベージコレクタの公開APIを追加したり、最適化を許可したりすることが、一般的なケースの改善を妨げることになると嘆きました。また、彼はJVMのように複数のGC実装をサポートしたくないと考えています。グレゴリー氏にとって、Python 3.14におけるインクリメンタルGCの変更は、あるGCが特定の用途に適している一方で、別のGCが適していないということを示しており、「そのようなケースは、私たちのテストスイートに含まれるべきだ」と述べています。

トビアス・ヴリグスタッド氏は、Javaで既に採用されている可能性のある最適化メカニズムを提案しました。それは、ガベージコレクタに明示的な「CPU予算」を提供するというものです。このメカニズムは「比較的直感的」ですが、残念ながら、ユーザーがGCを「すべての不要なメモリを回収できないように」調整することを可能にするという副作用もあります。

パブロ・ガリンド・サルガド氏は、GCの問題に直面し、本番環境でGCの世代をリアルタイムで最適化することで、即座のフィードバックを得ることに成功したと述べています。トーマス・ヴウターズ氏は、他のSteering Councilのメンバーを驚かせながら、パブロ氏の意見に賛同し、ガベージコレクタを最適化して「異常なGCの動作を修正する」ことができると述べました。また、これはPythonのバージョンアップを妨げる一般的な要因であるとも付け加えました。マーク氏は、これらのデータに関心を示し、「決定を下す前に、これらの情報を収集しましょう」と述べています。

ラリー・ハスティングス氏は、並行処理型または「ロックフリー」のガベージコレクタについて質問し、Pythonでそれが実現する可能性について尋ねました。「無料でメモリが戻ってくる」というのは素晴らしい宣伝文句に聞こえます。しかし、残念ながら、実装が「非常に難しい」というだけでなく(ラリー氏は「あなたは賢いと聞いた」と反論しました)、マーク氏は、C拡張機能は「何でもできる」ため、ロックフリーの並行型ガベージコレクタの動作を妨げる可能性があると述べました。トビアス氏は、並行型ロックフリーのガベージコレクタは「非常に侵襲的」であり、すべてのポインタにメタデータを追加する必要がある、という意見に同意しました。