Pythonのリストにおける処理パフォーマンス

twitterのTLをふと見たら[twitter:@kk6]さんが

とつぶやいていた。
個人的にはmap()関数の方が読みやすいという意見には賛成。
しかしパフォーマンスについては議論されていなかったようなので検証してみた。

とりあえずtimeit

今回はとりあえず上記にあったように整数の最小値から整数の最大値までの整数を乱数で10000個生成し、負数であったら0にして新しいリストを生成するという至って簡単な非破壊の仕様にしました。
ということで今回のパフォーマンス計測に使ったコード。

# coding:utf-8

import random
import sys
from timeit import Timer
from itertools import imap

data =  [random.randint(-sys.maxint - 1, sys.maxint) for x in xrange(0, 10000)]
   
def for_filter():
    return [x if x > 0 else 0 for x in data]
   
def map_filter():
    return map(lambda x: x if x > 0 else 0, data)
   
def imap_filter():
    return [x for x in imap(lambda x: x if x > 0 else 0, data)]
   
print Timer('for_filter()', 'from %s import data, for_filter' % __name__).timeit(10000)
print Timer('map_filter()', 'from %s import data, map_filter, minus_to_zero' % __name__).timeit(10000)
print Timer('imap_filter()', 'from %s import data, imap_filter, minus_to_zero' % __name__).timeit(10000)

今回測定に使ったPythonとマシンのスペック。


Python:2.7.2(MacPortsでインストールしたもの)
OS:Mac OS X 10.7.2
CPU:Core i7 2.0GHz(MBP15 early2011下位モデル)
Memory: 8GB


実行結果は

10.2765209675
17.9944810867
23.7831270695

となり、早い順にリスト内包表記, map, imapとなりました。
最初はmap()の方が早いんじゃないかと思ってたけどmap()はそんなに早くなく、
むしろリスト内包表記の方が1.8倍の速度出てます。

リスト内包表記が早い理由については、[twitter:@methane]さんが解説されている記事に詳しく載っています。
DSAS開発者の部屋:Pythonの内包表記はなぜ速い?

結論

個人的にはmap()関数の方が可読性は高いと思います。
その一方でリスト内包表記はやっぱりよいパフォーマンスでとりあえずこっち使っとけばそれなりのパフォーマンスが出ます。
以上の点を踏まえてケースバイケースで対応する方が良さそうです。

2011/10/19追記

[twitter:@kk6]さんからいただいたコメントにあるコードに合わせてみたところ結果が変わったので追記。
コードは少し端折ってます。

def for_filter():
    return [x if x > 0 else 0 for x in data]

def map_filter():
    return map(lambda x: x if x > 0 else 0, data)

def imap_filter():
    return list(imap(lambda x: x if x > 0 else 0, data))

print Timer('for_filter()', 'from %s import data, for_filter' % __name__).timeit(10000)
print Timer('map_filter()', 'from %s import data, map_filter' % __name__).timeit(10000)
print Timer('imap_filter()', 'from %s import data, imap_filter' % __name__).timeit(10000)

そして結果は

10.1584570408
17.7165570259
16.8313369751

となり、リスト内包表記 > imap() > map()という順になりました。
早くなってる理由はリストの要素数が分かってるから最初からその要素数に合わせて領域を拡張しなくてもいいようにしてるとかそんな理由でしょうか?