最近、Python3を使い始めたのですが、xrangeがないのが痛いですね。
簡単な例です。
*1)** Python2:
from time import time as t
def count():
st = t()
[x for x in xrange(10000000) if x%4 == 0]
et = t()
print et-st
count()
2) Python3:
from time import time as t
def xrange(x):
return iter(range(x))
def count():
st = t()
[x for x in xrange(10000000) if x%4 == 0]
et = t()
print (et-st)
count()
結果は、それぞれ
1) 1.53888392448 2) 3.215819835662842
なんでだろう?というか、なぜxrange'が削除されたのか?こんなに素晴らしいツールがあったんですね。私のような初心者にとっては、誰もがどこかでそうだったように。なぜ削除されたのでしょうか?誰か適切なPEPを教えてくれませんか、見つかりません。
ありがとうございます。
いくつかのパフォーマンス測定では、time
を使って手動で行う代わりに、timeit
を使用しています。
まず、Apple 2.7.2 64ビット。
In [37]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.05 s per loop
今度はpython.org 3.3.0 64bitです。
In [83]: %timeit collections.deque((x for x in range(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.32 s per loop
In [84]: %timeit collections.deque((x for x in xrange(10000000) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.31 s per loop
In [85]: %timeit collections.deque((x for x in iter(range(10000000)) if x%4 == 0), maxlen=0)
1 loops, best of 3: 1.33 s per loop
どうやら、3.xのrange
は2.xのxrange
よりも本当に少し遅いようです。そして、OPのxrange
関数はそれとは関係ありません。(驚くことではありません。__iter__
スロットへの1回限りの呼び出しは、ループ内で起こることへの10000000回の呼び出しの中では見えそうにありませんが、誰かが可能性としてそれを持ち出しました)。
でも,30%しか遅くなりません。なぜOPは2倍遅くなったのか?32ビットのPythonで同じテストを繰り返すと、1.58対3.12という結果になりました。私の推測では、これは 3.x が 64 ビットのパフォーマンスのために最適化されていて、32 ビットに悪影響を与えているケースの一つではないかと思います。
しかし、それが本当に重要なのでしょうか?3.3.0 を 64 ビットに戻してチェックしてみましょう。
In [86]: %timeit [x for x in range(10000000) if x%4 == 0]
1 loops, best of 3: 3.65 s per loop
つまり、list
の構築には、反復処理全体の2倍以上の時間がかかるのです。
また、「Python 2.6+よりもはるかに多くのリソースを消費する」についてですが、私のテストによると、3.xのrange
は2.xのxrange
とまったく同じ大きさのようで、仮に10倍の大きさであったとしても、不要なリストを構築することは、rangeの反復でできることよりも10000000倍ほど問題が大きいです。
また、deque
の中のCループの代わりに、明示的なfor
ループはどうでしょうか?
In [87]: def consume(x):
....: for i in x:
....: pass
In [88]: %timeit consume(x for x in range(10000000) if x%4 == 0)
1 loops, best of 3: 1.85 s per loop
つまり、for
文で、range
を反復する実際の作業とほぼ同じだけの時間を無駄にしているのです。
もしあなたが range オブジェクトの反復処理の最適化を心配しているのであれば、それはおそらく間違った場所を見ているのでしょう。
一方、あなたは何度同じことを言われても、なぜxrange
が削除されたのかと聞き続けていますが、もう一度言います。削除されたのではなく、range
に改名されたのであり、2.xのrange
こそが削除されたのです。
3.3 range
オブジェクトが、2.x xrange
オブジェクトの直系の子孫であることを証明するものがあります (2.x range
関数の子孫ではありません)。3.3 range
]1と2.7 xrange
のソースです。また、変更履歴も見ることができます(ファイル内の最後の文字列 "xrange" を置き換えた変更にリンクされていると思います)。
では、なぜ遅くなったのでしょうか?
それは、たくさんの新機能が追加されたからです。もうひとつは、細かい副作用のある変更をあちこちで(特に繰り返しの中で)行っていることが挙げられます。また、重要ではないケースをわずかに悲観的にすることがあっても、重要なケースを劇的に最適化するために多くの作業が行われてきました。これらを総合すると、`範囲
をできるだけ速く反復することが、今では少し遅くなっていることに驚きはありません。これは、誰もが注目するほど重要ではないケースのひとつです。現実のユースケースで、この性能差がコードのホットスポットになるような人はいないでしょう。
Python 3 の range
型は Python 2 の xrange
と同様に動作します。なぜ遅くなるのかよくわかりません。なぜならば、xrange
関数が返すイテレータは、range
を直接イテレートした場合に得られるものと同じだからです。
私のシステムでは速度低下を再現することができません。以下は私がテストした方法です。
Python 2, xrange
を使用。
Python 2.7.3 (default, Apr 10 2012, 23:24:47) [MSC v.1500 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in xrange(1000000) if x%4]",number=100)
18.631936646865853
Python 3, with range
はほんの少しだけ速くなります。
Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> import timeit
>>> timeit.timeit("[x for x in range(1000000) if x%4]",number=100)
17.31399508687869
最近知ったのですが、Python 3 の range
型には、スライスのサポートなど、他にもいくつかの優れた機能があります。例えば、range(10,100,2)[5:25:5]
は range(20, 60, 10)
!