2012年5月28日 星期一

[Python]for loop, list comprehension, map效能比較

在Python中要走訪一個list有很多種方法,常見的有for loop, list comprehension, map
但是究竟哪一種方法效能比較好呢?以下是一個範例程式可以做個比較:
import time, sys
reps = 10000
repsList = range(reps)

def tester(func, *args):
    startTime = time.time()
    for i in range(reps):
        func(*args)
        elapsed = time.time() - startTime
        return elapsed

def forLoop():
    res = []
    for x in repsList:
        res.append(abs(x))

def listComprehension():
    res = [abs(x) for x in repsList]

def mapFunction():
    res = list(map(abs, repsList))

print (sys.version)
tests = (forStatement, listComprehension, mapFunction)
for testfunc in tests:
    print (testfunc.__name__.ljust(20), '=>', tester(testfunc))
這段範例程式會用三種方法分別建立一個內含10000個element的list,並且對每個element執行內建函式abs()
以下是程式執行結果,執行環境為Python 3.2.2
3.2.2 (default, Dec 23 2011, 11:21:15)
[GCC 4.6.1]
forLoop              => 0.0016720294952392578
listComprehension    => 0.0008997917175292969
mapFunction          => 0.0007519721984863281
由以上執行結果可知map最快、list comprehension次之、for loop大概要多花前面兩種方法一倍的時間才能執行完

為什麽map和list comprehension會跑這麼快呢?
其實是因為這兩種方法都有經過Python直譯器的最佳化,在執行時很接近C程式碼跑得速度
而for loop沒有經過最佳化直接在Python直譯器中執行,自然就跑得比較慢

以上範例是針對Python內建函式(abs)所做的測試,如果改用user自己定義的函式結果會不會不一樣呢?
下列範例會把list中的element都加上1
import time, sys
reps = 10000
repsList = range(reps)

def tester(func, *args):
    startTime = time.time()
    for i in range(reps):
        func(*args)
        elapsed = time.time() - startTime
        return elapsed

def forLoop():
    res = []
    for x in repsList:
        res.append(x + 1)

def listComprehension():
    res = [x + 1 for x in repsList]

def mapFunction():
    res = list(map((lambda x : x + 1), repsList))

print (sys.version)
tests = (forLoop, listComprehension, mapFunction)
for testfunc in tests:
    print (testfunc.__name__.ljust(20), '=>', tester(testfunc))
3.2.2 (default, Dec 23 2011, 11:21:15)
[GCC 4.6.1]
forLoop              => 0.0016200542449951172
listComprehension    => 0.0009100437164306641
mapFunction          => 0.0018360614776611328
執行結果出乎意料,map竟然變得比fop loop還慢。而list comprehension還是一樣快了快一倍
由此結果可知,Python直譯器當map裡頭放的不是內建函式時,並不會做最佳化的動作
當使用user自己定義的函式時,用list comprehension才能得到最佳的效能

這篇文章結論如下:
1. 當用到內建函式時,使用map可以得到最佳的效能
2. 除了1的case以外,盡量使用list comprehension才可以得到最佳的效能
3. for loop不管在什麽情況下都是跑最慢的
4. 最重要的是除非有特殊效能的考量,否則以上這三種方法盡量優先考慮Python程式的可讀性

Reference: Learning Python, 4e

2 則留言: