このようにマイクロ最適化する必要がある場合、何が最速かを知る唯一の方法はテストすることです。
短いバージョンは次のとおりです:append
は よりも高速でextend
、Joran Beasley の提案itertools.chain.from_iterable
はどちらよりもわずかに高速ですが、map
をリスト内包表記に置き換えた場合のみです。
そう:
import itertools
import timeit
def makestuff(count):
for i in range(count):
yield (i, i)
def f_extend(mystuff):
mylist = []
for x, y in mystuff:
mylist.extend([int(x), int(y)])
return mylist
def f_append(mystuff):
mylist = []
for x, y in mystuff:
mylist.append(int(x))
mylist.append(int(y))
return mylist
def f_chainmap(mystuff):
return list(map(int, itertools.chain(*mystuff)))
def f_chaincomp(mystuff):
return [int(x) for x in itertools.chain(*mystuff)]
def f_chainfrommap(mystuff):
return list(map(int, itertools.chain.from_iterable(mystuff)))
def f_chainfromcomp(mystuff):
return [int(x) for x in itertools.chain.from_iterable(mystuff)]
def f_reducecompcomp(mystuff):
return [int(x) for x in reduce(operator.iadd, (list(y) for y in mystuff), [])]
def f_reducecompmap(mystuff):
return [int(x) for x in reduce(operator.iadd, map(list, mystuff), [])]
try:
import numpy
def f_numpy(mystuff):
return numpy.array(mystuff).flatten().tolist()
def f_numpy2(mystuff):
return numpy.array(list(mystuff)).flatten().tolist()
except:
pass
if __name__ == '__main__':
import sys
main = sys.modules['__main__']
count = int(sys.argv[1]) if len(sys.argv) > 1 else 10000
for f in dir(main):
if f.startswith('f_'):
func = getattr(main, f)
mystuff = makestuff(count)
testfunc = lambda: func(mystuff)
print('{}: {}'.format(f, timeit.timeit(testfunc, number=count)))
Python 2 についてはmap
、extra なしのバージョンを試してみたところ、list
わずかに高速になりましたが、まだほとんど競争力がありませんでした。もちろん、Python 3 の場合はlist
が必要です。
ここに私のタイミングがあります:
$ python testlister.py 1000000
f_append: 1.34638285637
f_chaincomp: 2.12710499763
f_chainfromcomp: 1.20806899071
f_chainfrommap: 2.77231812477
f_chainmap: 3.67478609085
f_extend: 1.38338398933
f_numpy: 5.52979397774
f_numpy2: 7.5826470852
f_reducecompcomp: 2.17834687233
f_reducecompmap: 3.16517782211
$ python3 ./testlister.py 1000000
f_append: 0.9949617639649659
f_chaincomp: 2.0521950440015644
f_chainfromcomp: 0.9724521590862423
f_chainfrommap: 2.5558998831082135
f_chainmap: 3.5766013460233808
f_extend: 1.149905970087275
f_reducecompcomp: 2.2112889911513776
f_reducecompmap: 1.9317334480583668
私python
は Apple の標準の Python 2.7.2 でpython3
あり、python.org 3.3.0 はどちらも 64 ビットで、両方とも OS X 10.8.2 で、2012 年半ばの MacBook Pro (2.2GHz i7 および 4GB) に搭載されています。
POSIX プラットフォームで 32 ビットの Python を使用している場合、それほど遠くない過去のどこかで、反復子が最適化されitertools
、64 ビットのビルドで多くのことが高速化されたように見えることに気付きました。 、しかし32ビットでは遅くなりました。append
したがって、その場合はそれが勝つかもしれません。(いつものように、実際に最適化したいプラットフォームでテストしてください。)
Ashwini Chaudharyは Python での浅いリストのフラット化にリンクし、さらにPython 連想リストで要素を効率的に検索することにリンクしました。私の結果と彼らの結果の違いの一部は、2.6.0 と 2.7.2/3.3.0 の間の反復子の改善にあると思いますが、大きな要素ではなく 2 要素要素を明示的に使用しているという事実は、おそらくさらに重要です。 .
また、回答の少なくとも 1 つがreduce
最速であると主張しました。元の投稿のreduce
実装はすべて非常に遅いですが、より高速なバージョンを思いつくことができました。彼らはまだappend
またはchain.from_iterable
と競争力がありませんが、正しい範囲にいます.
f_numpy
関数は heltonbiker の実装です。mystuff
は 2D イテレータであるため、これは実際にはイテレータをラップする 0D 配列を生成するだけなので、できるnumpy
ことはオーバーヘッドを追加することだけです。イテレータの 1D 配列を生成する実装を考え出すことができましたが、それはさらに遅くなりました。現在でnumpy
は、オーバーヘッドを N 倍の頻度で追加することしかできないためです。list
整数の 2D 配列を取得できる唯一の方法は、 のように最初に呼び出すことでしたf_numpy2
。これにより、処理がさらに遅くなりました。(公平を期すためlist
に、他の関数にエクストラを投げると速度も低下しましたが、 の場合ほど悪くはありませんでしたnumpy
。)
ただし、ここで空白にしている可能性は十分にあり、ここには合理的な使用方法がありnumpy
ます。もちろん、トップ レベルmystuff
または の各要素mystuff
が alist
または atuple
であると確信できる場合は、より良いものを作成できます。また、アプリを再設計して、numpy.array
シーケンスの一般的なシーケンスではなく、最初に 2D を使用できる場合。 、それはまったく別の話になります。しかし、シーケンスの一般的な 2D 反復があるだけの場合、このユース ケースにはあまり適していないようです。