Hatena::ブログ(Diary)

logging.info(self)

2011-10-18

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

| 23:03 | Pythonのリストにおける処理パフォーマンス を含むブックマーク

twitterのTLをふと見たら@さんが

とつぶやいていた。

個人的には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倍の速度出てます。

リスト内包表記が早い理由については、@さんが解説されている記事に詳しく載っています。

DSAS開発者の部屋:Pythonの内包表記はなぜ速い?


結論

個人的にはmap()関数の方が可読性は高いと思います。

その一方でリスト内包表記はやっぱりよいパフォーマンスでとりあえずこっち使っとけばそれなりのパフォーマンスが出ます。

以上の点を踏まえてケースバイケースで対応する方が良さそうです。

2011/10/19追記

@さんからいただいたコメントにあるコードに合わせてみたところ結果が変わったので追記。

コードは少し端折ってます。

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()という順になりました。

早くなってる理由はリストの要素数が分かってるから最初からその要素数に合わせて領域を拡張しなくてもいいようにしてるとかそんな理由でしょうか?

kk6kk6 2011/10/19 02:23 思ったよりパフォーマンスに差がでるんですね。
それと、imap_filter関数に関しては

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

とするとちょっとだけ速くなりました。それでもmap関数よりも遅いですが。

aroma_blackaroma_black 2011/10/19 23:09 コメントいただいた内容を更新しました。
私の環境だとmap()関数より早くなりだいぶマシになりました。

t2y-1979t2y-1979 2011/11/03 19:52 ピュア Python の関数だと、リスト内包表記の方が速いようです。組み込み関数を使うと、map の方が速い結果が出るかもしれません。

以下の最適化のところでも少し触れています。
http://gihyo.jp/news/report/01/europython2011/0002

aroma_blackaroma_black 2011/11/05 13:43 EuroPythonの記事の該当コード実行してみたところ、確かに組み込み関数だとmapの方が早い結果になりました。
組み込み関数となると使える場面は限られてしまいますが、リスト内包表記との使い分けの基準は明確に分けれそうですね。

トラックバック - http://d.hatena.ne.jp/aroma_black/20111018/1318946634
リンク元