2013-01-02 93 views
4

我希望儘可能使用嵌套函數而不是Python中的方法或全局函數。所以我決定測試它們的性能,因爲它接合了當你在另一個函數中定義一個函數時,在外函數的每次調用中定義內函數都會有開銷。嵌套函數比Python中的全局函數更快嗎?

充其量我希望全局函數稍微快一點,但令人驚訝的是嵌套函數更快。有誰知道爲什麼?

這是我的代碼:

from time import clock 

def a(n): 
    return n + 1 

def b1(loopcount): 
    return sum([a(n) for n in range(loopcount)]) 

def b2(loopcount): 
    def a(n): 
     return n + 1 
    return sum([a(n) for n in range(loopcount)]) 

powers = [5, 6, 7] 
b1times = [] 
b2times = [] 
print " ", "".join(["{:^10d}".format(n) for n in powers])  
for i in range(5): 
    for power in powers: 
     t = clock() 
     b1(10**power) 
     b1times.append(clock() - t) 
    for power in powers: 
     t = clock() 
     b2(10**power) 
     b2times.append(clock() - t) 
    print "b1:", "".join(["{:^10.5f}".format(n) for n in b1times]) 
    print "b2:", "".join(["{:^10.5f}".format(n) for n in b2times]) 
    print "" 
    b1times = [] 
    b2times = [] 

這是我的計算機上的結果:使用@Janne Karila的評論

現在我:

 5   6   7 
b1: 0.08200 0.82773 8.47946 
b2: 0.06914 0.79637 8.18571 

b1: 0.07332 0.82139 8.68262 
b2: 0.06547 0.82088 8.19606 

b1: 0.07963 0.82625 9.65037 
b2: 0.06617 0.82027 8.21412 

b1: 0.07630 0.82112 8.49082 
b2: 0.06541 0.80686 8.20532 

b1: 0.12328 0.87034 8.42964 
b2: 0.07059 0.79717 8.24620 

UPDATE多稱呼b1和b2,b1變得更快。正如@Kos和@Pavel Anossov在他們的回答中所說的那樣,一些因素會影響這裏的速度,所以你不能做出一般性陳述。
謝謝大家!

from time import * 

def a1(n): 
    return n + 1 

def b1(n): 
    return a1(n) 

def b2(n): 
    def a2(): 
     return n + 1 
    return a2() 

powers = [4, 5, 6] 
b1times = [] 
b2times = [] 
print " ", "".join(["{:^10d}".format(n) for n in powers])  
for i in range(5): 
    for power in powers: 
     t = clock() 
     sum([b1(n) for n in range(10**power)]) 
     b1times.append(clock() - t) 
    for power in powers: 
     t = clock() 
     sum([b2(n) for n in range(10**power)]) 
     b2times.append(clock() - t) 
    print "b1:", "".join(["{:^10.5f}".format(n) for n in b1times]) 
    print "b2:", "".join(["{:^10.5f}".format(n) for n in b2times]) 
    print "" 
    b1times = [] 
    b2times = [] 
+4

比滾動自己計時的東西相反,你應該使用[了'timeit'模塊(http://docs.python.org/3.3/library/timeit.html)。 –

+0

也,我認爲你應該調用你的內部函數的東西是不同於外部函數。稱他們都是「a」可能會在某個地方產生誤導。 –

+0

您的每個時機都是對外部函數的單個調用,這會對內部函數進行多次調用。您沒有以這種方式計算外部函數的開銷。 –

回答

7

有幾個部分的是這裏有一個效果:

1)定義函數(創建函數對象)的時間,
2)按名稱來查找函數對象的時間,
3)實際調用函數的時間。

你的整體功能例子是1)(無需在每次調用重新ab1)更快。但是,它在2)中較慢,因爲全局變量查找比本地查找慢。

爲什麼我們不能都有呢?

我已經extended your benchmark與使用全局函數,但避免使用本地變量中的全局查找解決方案。它似乎是我的機器上的三個中最快的:

 5   6   7 
b1: 0.04147 0.44421 4.46508 
b2: 0.03399 0.43321 4.41121 
b3: 0.03258 0.41821 4.25542 

b1: 0.03240 0.42998 4.39774 
b2: 0.03320 0.43465 4.42229 
b3: 0.03155 0.42109 4.23669 

b1: 0.03273 0.43321 4.37266 
b2: 0.03326 0.43551 4.42208 
b3: 0.03137 0.42356 4.25341 

b1: 0.03253 0.43104 4.40466 
b2: 0.03401 0.43719 4.42996 
b3: 0.03155 0.41681 4.24132 

b1: 0.03244 0.42965 4.37192 
b2: 0.03310 0.43629 4.42727 
b3: 0.03117 0.41701 4.23932 
+1

您不需要使用因爲你沒有重寫'a',所以''全球a''(除非你強調這是一個全球性的讀者)。 –

0

一般來說,不會,因爲每次運行外部函數時,內部函數都必須重新定義。這就是說,性能真的沒有關係,除非你能夠證明它(例如,一個特定的循環太慢,導致性能問題) - 所以我建議使用最可讀的 - 過早優化是一個壞事。

這就是說,我認爲全球的功能可能是一個更好的解決方案,因爲他們更容易在你的代碼重用,我要說更易讀 - 畢竟,扁平比嵌套更好。

+1

但是,如果在函數2中僅使用函數1時,在函數2中定義函數1,則將具有更清潔的全局名稱空間。 – nima

+1

@nima是的,但是如果你有那麼多重要的函數,你應該把它們分解成模塊 - 這比在函數中做很多深層嵌套函數要清楚得多。 –

+1

然後當你想使用一個簡單的功能時,你將不得不導入多個模塊。如果使用嵌套函數,則可以導入*一個模塊,並只向名稱空間添加一些重要函數。但這只是我個人的偏好;) – nima

7

LOAD_GLOBAL比LOAD_FAST慢得多。

B1

5   0 LOAD_GLOBAL    0 (sum) 
       3 BUILD_LIST    0 
       6 LOAD_GLOBAL    1 (range) 
       9 LOAD_FAST    0 (loopcount) 
      12 CALL_FUNCTION   1 
      15 GET_ITER 
     >> 16 FOR_ITER    18 (to 37) 
      19 STORE_FAST    1 (n) 
      22 LOAD_GLOBAL    2 (a) 
      25 LOAD_FAST    1 (n) 
      28 CALL_FUNCTION   1 
      31 LIST_APPEND    2 
      34 JUMP_ABSOLUTE   16 
     >> 37 CALL_FUNCTION   1 
      40 RETURN_VALUE 

B2

8   0 LOAD_CONST    1 (<code object a at 01E57A40, ...>) 
       3 MAKE_FUNCTION   0 
       6 STORE_FAST    1 (a) 

10   9 LOAD_GLOBAL    0 (sum) 
      12 BUILD_LIST    0 
      15 LOAD_GLOBAL    1 (range) 
      18 LOAD_FAST    0 (loopcount) 
      21 CALL_FUNCTION   1 
      24 GET_ITER 
     >> 25 FOR_ITER    18 (to 46) 
      28 STORE_FAST    2 (n) 
      31 LOAD_FAST    1 (a) 
      34 LOAD_FAST    2 (n) 
      37 CALL_FUNCTION   1 
      40 LIST_APPEND    2 
      43 JUMP_ABSOLUTE   25 
     >> 46 CALL_FUNCTION   1 
      49 RETURN_VALUE 

的差異無論是創建一個函數每次取決於函數(多少次你用LOAD_GLOBAL)組成。

 

創建的本地引用全局函數在一個內部循環使用是一種比較常見的優化:

def a(n): 
    return n + 1 

def b1(loopcount): 
    local_a = a 
    return sum([local_a(n) for n in range(loopcount)]) 

這仍然應該是不是找了一個全球性的,比更易於維護更快嵌套函數。

+0

+1用於說明與'dis' – Kos

+0

+1有關的常見優化問題。在這種情況下的另一個優化是擺脫這個函數,並使用'sum(range(1,loopcount + 1))';-),但我想這不是OP正在尋找 – mgilson

0

我認爲這是由python搜索對象定義的順序造成的。

每當解釋是否滿足對象的名字,它會首先搜索本地對象定義字典裏面記錄對象名稱和對象本身之間的映射。如果沒有在當地字典中找到,那麼全球化,然後內建。

而且,python中的所有東西都是對象,包括函數。