問題
n**2
より小さいすべての数値を見つけたいとします20000000
。
私がテストする 3 つのバリアントすべての一般的な設定:
import time, psutil, gc
gc.collect()
mem_before = psutil.virtual_memory()[3]
time1 = time.time()
# (comprehension, generator, function)-code comes here
time2 = time.time()
mem_after = psutil.virtual_memory()[3]
print "Used Mem = ", (mem_after - mem_before)/(1024**2) # convert Byte to Megabyte
print "Calculation time = ", time2 - time1
これらの数値を計算するには 3 つのオプションがあります。
1. 理解によるリストの作成:
x = [i**2 for i in range(20000000)]
それは本当に遅くて時間がかかります:
Used Mem = 1270 # Megabytes
Calculation time = 33.9309999943 # Seconds
2. 以下を使用してジェネレータを作成する'()'
:
x = (i**2 for i in range(20000000))
オプション 1 よりもはるかに高速ですが、それでも大量のメモリを使用します。
Used Mem = 611
Calculation time = 0.278000116348
3. ジェネレータ関数の定義(最も効率的):
def f(n):
i = 0
while i < n:
yield i**2
i += 1
x = f(20000000)
消費量:
Used Mem = 0
Calculation time = 0.0
質問は次のとおりです:
- 最初のソリューションと 2 番目のソリューションの違いは何ですか? を使用すると
()
ジェネレーターが作成されますが、なぜ大量のメモリが必要になるのですか? - 3 番目のオプションに相当する組み込み関数はありますか?
ベストアンサー1
他の人がコメントで指摘しているように、
range
はlist
Python 2 で を作成します。したがって、メモリを消費するのはジェネレータ自体ではなく、range
ジェネレータが使用する です。x = (i**2 for i in range(20000000)) # builds a 2*10**7 element list, not for the squares , but for the bases >>> sys.getsizeof(range(100)) 872 >>> sys.getsizeof(xrange(100)) 40 >>> sys.getsizeof(range(1000)) 8720 >>> sys.getsizeof(xrange(1000)) 40 >>> sys.getsizeof(range(20000000)) 160000072 >>> sys.getsizeof(xrange(20000000)) 40
これは、2 番目のバージョン (ジェネレータ式) が最初のバージョン (リストの内包表記) の約半分のメモリを使用する理由も説明しています。最初のバージョンでは 2 つのリスト (基数と平方数用) が構築されるのに対し、2 番目のバージョンでは基数用に 1 つのリストのみが構築されるためです。
xrange(20000000)
したがって、遅延反復可能オブジェクトを返すため、メモリ使用量が大幅に改善されます。これは本質的に、3 番目のバージョンを反映した、一連の数値を反復処理するための組み込みのメモリ効率の高い方法です (、およびの柔軟性が追加されていstart
ますstop
)step
。x = (i**2 for i in xrange(20000000))
Python 3 では、基本的には Python 2 にあったものと同じ
range
です。ただし、Python 3 オブジェクトには、スライスや contains など、Python 2 にはない優れた機能がいくつかあります。xrange
range
xrange
O(1)