2012-12-07 1 views
12

PEP 412は、Python 3.3で実装されており、属性辞書の処理が改善され、クラスインスタンスのメモリフットプリントが効果的に削減されています。 __slots__は同じ目的のために設計されたものですから、__slots__を使用している点はありますか?PEP 412は__slots__を冗長にしますか?

自分自身の答えを見つけるための試みで、私は次のテストを実行しますが、結果はあまり意味がありません。

class Slots(object): 
    __slots__ = ['a', 'b', 'c', 'd', 'e'] 
    def __init__(self): 
     self.a = 1 
     self.b = 1 
     self.c = 1 
     self.d = 1 
     self.e = 1 

class NoSlots(object): 
    def __init__(self): 
     self.a = 1 
     self.b = 1 
     self.c = 1 
     self.d = 1 
     self.e = 1 

のPython 3.3の結果:

>>> sys.getsizeof([Slots() for i in range(1000)]) 
Out[1]: 9024 
>>> sys.getsizeof([NoSlots() for i in range(1000)]) 
Out[1]: 9024 

のPython 2.7結果:

>>> sys.getsizeof([Slots() for i in range(1000)]) 
Out[1]: 4516 
>>> sys.getsizeof([NoSlots() for i in range(1000)]) 
Out[1]: 4516 

私はサイズが少なくともPython 2.7と異なると予想していたので、私はそこにいると仮定します。テストに何か問題がある。

+0

実際の状況の違いをまだ測定しましたか? :-)また、 '__slots__'は副作用のために(ab)使用することができます。 –

+0

はい、私は__slots__の問題を認識していますが、それは特定のユースケースに関連するよりも学問的な疑問でした。私はいくつかのテストを実行しようとしましたが、Python 3.3または2.7では、スロットを使用するかどうかに違いはありませんでした。しかし、おそらく私のテストは間違っているので、私もそれを投稿します。 – aquavitae

答えて

4

いいえ、PEP 412は、ではありません。__slots__は冗長です。


まず、Armin Rigoは正しく測定していません。測定する必要があるのは、オブジェクトのサイズと値に加えて、__dict__自体(NoSlotsの場合)とキー(NoSlotsの場合のみ)です。

それとも、彼は示唆して何を行う可能性:

cls = Slots if len(sys.argv) > 1 else NoSlots 
def f(): 
    tracemalloc.start() 
    objs = [cls() for _ in range(100000)] 
    print(tracemalloc.get_traced_memory()) 
f() 

私はOS X上で64ビットのCPythonの3.4でこれを実行すると、私はNoSlotsSlotsため25624872ため8824968を取得します。だから、Slotsインスタンスは256バイトを取る一方、NoSlotsインスタンスは88バイトを取るように見えます。


これはどのように可能ですか?

__slots__とキー分割__dict__にはまだ2つの違いがあるため、

まず、辞書で使用されるハッシュテーブルは、2/3のフルサイズで保存され、指数関数的に大きくなり、最小サイズになるため、余分なスペースが必要になります。素敵にコメントされたsourceを見ると、5スロットのポインタの代わりに8つのハッシュバケットを持つことになります。

第2に、辞書自体はフリーではありません。それは標準的なオブジェクトヘッダー、カウント、および2つのポインタを持っています。それはたくさんのように聞こえるないかもしれませんが、あなただけのいくつかの属性を(最もオブジェクトのみいくつかの属性を持っていることに注意してください...)持っているオブジェクトの話をしているとき、辞書ヘッダーはハッシュテーブルと同じくらいの違いを作ることができます。

あなたの例では

そしてもちろん、値なので、ここに関与する唯一のコストは、オブジェクト自体、プラス5つのスロットまたは8つのハッシュバケットとdictのヘッダであるので、差はかなり劇的です。実際の生活では、__slots__はめったに役に立ちませんその多くのメリットがあります。


最後に、PEP 412のみ主張通知:

ベンチマークはどこについて考え

オブジェクト指向プログラムのためのメモリ使用量を20%〜10%減少することを示しています__slots__を使用してください。節約額が非常に大きいので、__slots__を使用しないとばかげたり、最後の15%を圧迫する必要があります。あるいは、ABCや他のクラスを構築していて、誰がサブクラス化していると思われますか。サブクラスは節約が必要な場合があります。いずれにせよ、そのような場合には、__slots__、あるいは2/3の利益を得ずに半分の利益を得ているという事実は、依然として十分ではありません。あなたはまだ__slots__を使用する必要があります。

本当の勝利は、それが__slots__を使用して価値がない場合にはあります。あなたは無料で小さな利益を得るでしょう。

(確かに、__slots__から地獄を濫用しているプログラマーもいますが、この変更は、あなたが幸運なことであれば、他の何かを最適化してマイクロにエネルギーを入れるよう説得する可能性があります)

+1

'NoSlots'と' Slots'インスタンスのメモリサイズを指定しましたが、その順序は確実ですか? 'Slots'インスタンスは' NoSlots'インスタンスより軽くすべきではありませんか?これは私がWin 7 64ビットでPython 3.4で得たものです。 –

5

問題はsys.getsizeof()です。これはめったに期待どおりの結果を返しません。たとえば、この場合は、オブジェクトのサイズを計算せずに、オブジェクトの「サイズ」をカウントします(__dict__)。 100,000インスタンスを作成する際の実際のメモリ使用量を測定して再試行することをお勧めします。

Python 3.3の動作はPyPyの影響を受けている点に注意してください。__slots__は違いがないので、Python 3.3でも違いはないと思います。私が言う限りでは、__slots__は現在ほとんど使用されていません。

+0

私は64ビットPython 3.4で提案されたテストを実行しました。 'tracemalloc'によれば、' _(range)(100000)のスロット() 'は '8824968'を割り当て、' NoSlots'は '25624872'を割り当てます。私が愚かなことをしていないことを確認するには、[code](http://pastebin.com/EF96k4na)を参照してください。 – abarnert

+0

また、どのようにして_no_違いを生み出すのか分かりません。ハッシュテーブルを2/3しかロードしない(まだ間接インデックス配列を使って値を修正したり、最初の新しいコピーが見えるときにハッシュテーブルを圧縮するなどの方法で修正することができますが、それ以外の方法はありませんが)されている)。また、 'dict'ヘッダー自体はフリーではありません。小さなオーバーヘッドでもかまいませんが、最小サイズのテーブルと小さなint '1'への5つの参照と比較すると、オブジェクトの最大の部分です。 – abarnert

+0

私はCPython 3.3の実装を詳しく見ていませんでした。だから、__slots__を使うのになぜ大きな違いがあるのか​​は分かりません。私が確信できるのは、 '__slots__'はPyPy(PyPy2とPyPy3の両方)に全く違いがないということだけです。 –