Differences between PyPy and CPython¶

Differences related to garbage collection strategies¶

The garbage collectors used or implemented by PyPy is not based on reference counting, so when they are nolonger reachable, not instant freed the objects. この最も明白な影響は、ファイル(とソケットなど)がスコープ外になったときに速やかに閉じられないということです。 書き込み用にオープンされたファイルでは、データが出力バッファにしばらく残ってしまい、ディスク上のファイルが空か切り捨てられたように見えることがあります。 さらに、同時に開くファイルの数がOSの制限に達するかもしれません。

プログラム内のファイルが適切に閉じられないケースをデバッグする場合、-X track-resources コマンドラインオプションを使用することができます。 このオプションが指定された場合、ガベージコレクタがクローズしたファイルやソケットに対して ResourceWarning が生成される。 警告には、プログラムのどの部分が明示的にファイルを閉じていないかがわかりやすいように、ファイルまたはソケットが作成された位置のスタックトレースが含まれます。

CPython にこの違いを修正するには、ガベージコレクションに参照カウントのアプローチを強制しないと本質的に不可能です。 CPython で得られる効果は、言語設計の決定ではなく、実装の副作用として明確に説明されています: これに依存するプログラムは基本的にインチキです。 Jython や IronPython (または Java や .NET への Python の他の移植版) で採用される見込みがないことを考えると、言語仕様で CPython の動作を強制しようとするのは強すぎる制限でしょう。

OS の限界に危険なほど近づいているときに完全な GC を強制するという単純なアイデアでさえ、いくつかのケースでは非常に悪いことがあり得ます。 もしあなたのプログラムがオープンファイルを大量にリークするなら、それはうまくいくだろうが、n番目のリークファイルごとに完全なGCサイクルを強制する。 nの値は定数ですが、プログラムは任意の量のメモリを取ることができるので、完全なGCサイクルは恣意的に長くなります。 その結果、PyPy は実行時間のうち任意に大きな割合を GC に費やすことになり、実際の実行速度が 10% でも 100% でも 1000% でもなく、本質的に任意の倍率で遅くなります。 もしサードパーティのコードで発生した場合、これは作者に行き、問題を説明することを意味します: 彼らは、Python の非実装で実行するために、開いているファイルをクローズする必要があります。 この問題は __del__ メソッドが呼ばれる正確な時間に影響し、PyPy (Jython や IronPython も) では信頼性が低く、タイムリーではありません。 また、弱い参照が予想より少し長く生き続ける可能性があることを意味します。 これは “弱いプロキシ”(weakref.proxy()で返される)をやや使いにくいものにします:PyPyではもう少し長く生きているように見え、突然本当に死んでしまい、次のアクセスでReferenceErrorを発生させます。 弱いプロキシを使用するコードは、それらを使用するすべての場所で、そのようなReferenceErrorを注意深くキャッチしなければなりません。 (あるいは、より良い方法は、weakref.proxy() を全く使用しないことです。weakref.ref() を使用してください。)

weakref コールバックのドキュメントにおける詳細に注意してください:

f callback が提供されていて None でなく、返された weakref オブジェクトがまだ生存しているとき、オブジェクトが確定しようとするときコールバックが呼ばれるでしょう。

CPython の refcount セマンティックスのため、weakref が指すオブジェクトの直前か直後に死ぬ場合があります (典型的には何らかの循環参照で)。 もし、直後で死んだ場合は、コールバックが起動されます。 PyPyでの同様のケースでは、オブジェクトとweakrefの両方が同時に死んだとみなされ、コールバックは呼び出されません。 (Issue #2030)

GC の違いから、いくつかの余分な影響があります。 しかし、CPythonはオブジェクトが復活して再び死んだ場合、同じ__del__を何度も呼び出します(少なくとも古いCPythonではそうです。新しいCPythonではデストラクタを2回以上呼ばないようにしていますが、反対の例もあります)。 しかし、CPythonとは異なり、オブジェクトが互いに参照しあっているデッドサイクルがある場合、それらの__del__メソッドはとにかく呼ばれます; CPythonは代わりにそれらをgcモジュールのリストgarbageに置くでしょう。 より詳細な情報は、ブログ .

この違いは、いくつかのケースで間接的に現れるかもしれないことに注意してください。 例えば、途中で保留されたままのジェネレータは、-やはり- CPython よりも PyPy の方が遅くゴミとして回収されます。 もし、保留されたyieldキーワードがtry:with:ブロックに囲まれていれば、その違いを見ることができます。

デフォルトの GC (minimark と呼ばれる) を使うと、組み込み関数 id() は CPython と同じように動作します。

オブジェクトの長いチェーンがあり、それぞれが次のオブジェクトへの参照を持ち、それぞれが__del__を持つ場合、PyPyのGCは悪いパフォーマンスになることに注意してください。 明るい面では、他のほとんどの場合、ベンチマークはPyPyのGCがCPythonのGCよりもはるかに良いパフォーマンスを示すことを示しています。

もうひとつの違いは、既存のクラスに __del__ を追加しても呼び出されないことです:

>>>> class A(object):.... pass....>>>> A.__del__ = lambda self: None__main__:1: RuntimeWarning: a __del__ method added to an existing type will not be called

さらにわからないのは、古いスタイルのクラスで __del__ をインスタンスに付けても同じことが言えます (CPython でも新しいスタイルのクラスではうまくいかないのです)。 PyPyではRuntimeWarningが表示されます。

Last note: CPython はプログラム終了時に自動的に gc.collect() を実行しようとしますが、PyPy はそうではありません。 (CPythonとPyPyの両方で、すべてのオブジェクトが終了する前にいくつかのgc.collect()が必要なケースを設計することが可能です。 これはCPythonのアプローチをとにかく “ほとんどの場合 “機能させるだけです)

コメントを残す

メールアドレスが公開されることはありません。