對於規模更大、運行時間更長的數據分析應用程序,你可能會希望測試一下各個部分或函數調用或語句的執行時間。你可能會希望了解某個復雜計算過程中到底是哪些函數占用的時間最多。幸運的是,在開發和測試代碼的過程中,IPython能夠讓你輕松得到這些信息。使用內置的time模塊及其time.clock和time.time函數手工測試代碼執行時間是一件令人煩悶的事情,因為你必須編寫許多一模一樣的了無生趣的公式化代碼:
-
import time
-
start = time.time()
-
for i in range(iterations):
-
# 這里放一些待執行的代碼
-
elapsed_per = ( time.time() - start) / iterations
由於這是一個非常常用的功能,所以IPython專門提供了兩個魔術函數(%time和%timeit)以便自動完成該過程。%time一次執行一條語句,然后報告總體執行時間。假設我們有一大堆字符串,希望對幾個“能夠選出具有特殊前綴的字符串”的函數進行比較。下面是一個擁有60萬字符串的數組,以及兩個不同的“能夠選出其中以foo開頭的字符串”的方法:
-
# 一個非常大的字符串數組
-
strings = [ 'foo', 'foobar', 'baz', 'qux', 'python', 'Guido Van Rossum'] * 100000
-
method1 = [x for x in strings if x.startswith('foo')]
-
method2 = [x for x in strings if x[:3] == 'foo']
看上去它們的性能表現應該差不多,對吧?我們通過%time來確認一下:
-
In [561]: %time method1 = [x for x in strings if x.startswith('foo')]
-
CPU times: user 0.19 s, sys: 0.00 s, total: 0.19 s
-
Wall time: 0.19 s
-
-
In [562]: %time method2 = [x for x in strings if x[:3] == 'foo']
-
CPU times: user 0.09 s, sys: 0.00 s, total: 0.09 s
-
Wall time: 0.09 s
牆上時間(Wall time)是我們最感興趣的數字。所以,看上去第一個方法耗費了兩倍以上的時間,但這並不是一個非常精確的結果。如果你對相同語句多次執行%time的話,就會發現其結果是會變的。為了得到更為精確的結果,需要使用魔術函數%timeit。對於任意語句,它會自動多次執行以產生一個非常精確的平均執行時間。
-
In [563]: %timeit [x for x in strings if x.startswith('foo')]
-
10 loops, best of 3: 159 ms per loop
-
-
In [564]: %timeit [x for x in strings if x[:3] == 'foo']
-
10 loops, best of 3: 59.3 ms per loop
這個貌似平淡無奇的例子正好說明了一個事實:我們非常有必要了解Python標准庫、NumPy、pandas以及本書中所用到的其他庫的性能特點。在大型數據分析應用程序中,這些不起眼的毫秒數是會不斷累積的!
對於那些執行時間非常短(甚至是那些微秒(1e-6秒)或納秒(1e-9秒)級的)的分析語句和函數而言,%timeit是非常有用的。雖然這些時間值小到幾乎可以忽略不計,但同樣執行100萬次一個20微秒的函數,所用的時間要比一個5微秒的多15秒。在上面那個例子中,我們可以直接對那兩個字符串運算進行比較以了解其性能特點:
-
In [565]: x = 'foobar'
-
-
In [566]: y = 'foo'
-
-
In [567]: %timeit x.startswith(y)
-
1000000 loops, best of 3: 267 ns per loop
-
-
In [568]: %timeit x[:3] == y
-
10000000 loops, best of 3: 147 ns per loop