7

私は可能な限り、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 = [] 

そして、これは私のコンピューターでの結果です:

        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

更新: @Janne Karila のコメントを使用

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

4 に答える 4

11

ここに影響を与えるいくつかの部分があります:

1)関数を定義する時間(関数オブジェクトを作成する時間)、
2)関数オブジェクトを名前で検索する時間、
3)実際に関数を呼び出す時間。

グローバル関数の例は、1)aを使用すると高速になります(への呼び出しごとに再定義する必要はありませんb1)。ただし、2)では、グローバル変数のルックアップがローカルルックアップよりも遅いため、速度が遅くなります。

なぜ私たちは両方を持つことができないのですか?

グローバル関数を使用するソリューションでベンチマークを拡張しましたが、ローカル変数を使用したグローバルルックアップは回避しています。それは私のマシンの3つの中で最も速いようです:

        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
于 2013-01-02T12:47:07.693 に答える
10

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)])

これは、グローバルを検索するよりも高速で、ネストされた関数よりも保守しやすいはずです。

于 2013-01-02T12:46:30.247 に答える
0

Pythonでオブジェクト定義を検索する順番が原因だと思います。

インタプリタがオブジェクト名に出会うと、最初に、オブジェクト名とオブジェクト自体の間のマッピングを記録するローカルオブジェクト定義dictを検索します。ローカルdictで見つからない場合は、グローバル、次に組み込み。

そして、Pythonのすべてのものは関数を含むオブジェクトです。

于 2013-01-02T12:57:39.767 に答える
0

一般に、外側の関数が実行されるたびに、内側の関数を再定義する必要があるため、いいえ。

これは、パフォーマンスが実際に重要ではないことを示しています (たとえば、特定のループが遅すぎてパフォーマンスの問題を引き起こしているなど)。したがって、最も読みやすいものを使用することをお勧めします。

そうは言っても、グローバル関数はコード全体で再利用しやすいため、おそらくより良いソリューションであると主張します

于 2013-01-02T12:39:00.783 に答える