Python=で特定の回数ステートメントを実行する一般的な方法は、for
ループを使用することです。
これを行う一般的な方法は、
# I am assuming iterated list is redundant.
# Just the number of execution matters.
for _ in range(count):
pass
上記のコードが一般的な実装であると主張する人はいないと思いますが、別の選択肢があります。 Pythonの速度を使用して、参照を乗算してリストを作成します。
# Uncommon way.
for _ in [0] * count:
pass
古いwhile
方法もあります。
i = 0
while i < count:
i += 1
これらのアプローチの実行時間をテストしました。これがコードです。
import timeit
repeat = 10
total = 10
setup = """
count = 100000
"""
test1 = """
for _ in range(count):
pass
"""
test2 = """
for _ in [0] * count:
pass
"""
test3 = """
i = 0
while i < count:
i += 1
"""
print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))
# Results
0.02238852552017738
0.011760978361696095
0.06971727824807639
わずかな違いがあった場合、私は主題を開始しませんが、速度の違いは100%であることがわかります。 2番目の方法がより効率的であれば、なぜPythonはそのような使用を奨励しませんか?より良い方法はありますか?
テストはWindows 1およびPython 3.6で行われます。
@Tim Petersの提案に従って、
.
.
.
test4 = """
for _ in itertools.repeat(None, count):
pass
"""
print(min(timeit.Timer(test1, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test2, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test3, setup=setup).repeat(repeat, total)))
print(min(timeit.Timer(test4, setup=setup).repeat(repeat, total)))
# Gives
0.02306803115612352
0.013021619340942758
0.06400113461638746
0.008105080015739174
これははるかに良い方法を提供し、これは私の質問にほぼ答えます。
両方がジェネレーターであるため、これがrange
よりも高速なのはなぜですか。値が変わらないからでしょうか?
を使用して
for _ in itertools.repeat(None, count)
do something
は、すべての世界で最高のものを得るための非自明な方法です。小さな一定のスペース要件、および反復ごとに作成される新しいオブジェクトはありません。内部では、repeat
のCコードはネイティブC整数型(Python整数オブジェクトではありません!)を使用して、残りのカウントを追跡します。
そのため、カウントはプラットフォームC ssize_t
タイプ。通常は最大2**31 - 1
は32ビットボックスで、ここは64ビットボックスで:
>>> itertools.repeat(None, 2**63)
Traceback (most recent call last):
...
OverflowError: Python int too large to convert to C ssize_t
>>> itertools.repeat(None, 2**63-1)
repeat(None, 9223372036854775807)
これは私のループにとって十分に大きなものです;-)
最初の方法(Python 3)で)は、値の範囲を反復処理できる範囲オブジェクトを作成します(ジェネレーターオブジェクトに似ていますが、何度も反復処理できます)。値の範囲全体ではなく、現在の値と最大値のみが含まれ、最大値に達するか通過するまでステップサイズ(デフォルトは1)ずつ増加するため、多くのメモリを消費します。
range(0, 1000)
のサイズをlist(range(0, 1000))
のサイズと比較します: Try It Online! 。前者はメモリ効率が非常に高いです。サイズに関係なく48バイトしかかかりませんが、リスト全体はサイズの点で直線的に増加します。
2番目の方法は、高速ですが、私が過去に話したメモリを占有します。 (また、_0
_は24バイトを占め、None
は16バイトを占めるが、それぞれの_10000
_の配列は同じサイズを持っているようだ。興味深い。おそらくポインターであるため)
興味深いことに、_[0] * 10000
_はlist(range(10000))
よりも約10000小さくなります。これは、最初のものではすべてが同じプリミティブ値であるため、最適化できるからです。
3番目のものは、別のスタック値を必要としないため、ニースでもあります(range
を呼び出すには、呼び出しスタック上の別のスポットが必要です)。
itertools
がそのようにクールだからといって、最後のものが最速かもしれません:P正しく覚えていれば、Cライブラリの最適化を使用すると思います。
この答えは、便宜上ループ構成を提供します。 itertools.repeat
を使用したループに関する追加の背景については、Tim Petersの回答 上記 、Alex Martelliの回答 here およびRaymond Hettingerの回答 here を参照してください。
# loop.py
"""
Faster for-looping in CPython for cases where intermediate integers
from `range(x)` are not needed.
Example Usage:
--------------
from loop import loop
for _ in loop(10000):
do_something()
# or:
results = [calc_value() for _ in loop(10000)]
"""
from itertools import repeat
from functools import partial
loop = partial(repeat, None)
最初の2つの方法では各反復にメモリブロックを割り当てる必要がありますが、3番目の方法では各反復にステップを作成するだけです。
範囲は低速な関数であり、range(0,50)
など、速度を必要としない小さなコードを実行する必要がある場合にのみ使用します。 3つの方法を比較することはできないと思います。彼らは全く違います。
以下のコメントによると、最初のケースはPython 2.7、Python 3でのみ有効で、xrangeのように機能し、それぞれにブロックを割り当てません私はそれをテストしましたが、彼は正しいです。