【问题标题】:Are nested functions faster than global functions in Python?嵌套函数比 Python 中的全局函数快吗?
【发布时间】:2012-12-16 19:53:22
【问题描述】:

我更喜欢在 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 = [] 

【问题讨论】:

  • 你应该使用the timeit module,而不是滚动你自己的计时东西。
  • 另外,我认为您应该将内部函数称为与外部函数不同的东西。将它们都称为a 可能会在某些地方产生误导。
  • 您的每个计时都是对外部函数的一次调用,这会多次调用内部函数。您不会以这种方式计时外部函数中的开销。
  • @Janne Karila:好点。我已经更新了。

标签: python performance-testing nested-function


【解决方案1】:

这里有几个部分有影响:

1) 定义函数的时间(创建函数对象),
2) 按名称查找函数对象的时间,
3) 实际调用函数的时间。

您的全局函数示例使用 1) 更快(无需在每次调用 b1 时重新定义 a)。但是,在 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

【讨论】:

  • 你不需要使用global a,因为你没有重新绑定a(除非你这样做是为了强调它对读者来说是一个全球性的事实)。
【解决方案2】:

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

这应该仍然比查找全局函数更快,并且比嵌套函数更易于维护。

【讨论】:

  • +1 用于提及常见优化。在这种情况下,另一个优化是去掉这个函数并使用sum(range(1,loopcount+1)) ;-),但我想这不是 OP 真正想要的
【解决方案3】:

一般来说,不会,因为每次运行外部函数时,都必须重新定义内部函数。

这就是说,性能真的无关紧要,除非你能证明它确实如此(例如,一个特定的循环太慢并导致性能问题) - 所以我建议使用最易读的东西 - 过早的优化是不好的东西。

也就是说,我认为全局函数可能是更好的解决方案,因为它们更容易在您的代码中重用,而且我会说更具可读性 - 毕竟,扁平比嵌套更好。 p>

【讨论】:

  • 但是如果你在function2里面定义function1,而function1只用在function2中,你会有一个更干净的全局命名空间。
  • @nima 是的,但如果你有这么多重要的函数,你可能应该将它们拆分为模块——这比在函数中创建大量嵌套函数要清楚得多。
  • 那么当你想使用一个简单的特性时,你将不得不导入多个模块。如果您使用嵌套函数,您可以导入 * 单个模块,并且只将一些重要函数添加到您的命名空间。但这只是我猜的个人喜好;)
  • import * 几乎总是一个坏主意,因为它会污染您的命名空间。如果可以提供有用的结构,则导入更多模块并不是一件坏事。
【解决方案4】:

我认为这是由python中搜索对象定义的顺序引起的。

当解释器遇到一个对象名时,它会首先搜索本地对象定义字典,该字典记录了对象名和对象本身的映射关系。如果在本地字典中找不到,则为全局,然后为内置。

而且,python中的所有东西都是对象,包括函数。

【讨论】:

    猜你喜欢
    • 2017-04-26
    • 2020-02-01
    • 2012-10-15
    • 1970-01-01
    • 2011-04-08
    • 2019-11-21
    • 1970-01-01
    • 2016-04-03
    • 1970-01-01
    相关资源
    最近更新 更多