Python函数调用非常慢

Python函数调用非常慢,python,function,profiling,call,Python,Function,Profiling,Call,这主要是为了确保我的方法是正确的,但我的基本问题是,如果我需要访问函数,是否值得在函数外部进行检查。我知道,我知道,过早优化,但在许多情况下,将if语句放在函数调用中以确定是否需要运行其余代码,或者将其放在函数调用之前,这是不同的。换言之,这样或那样做都不需要付出任何努力。现在,所有的检查都混合在两者之间,我希望这一切都是好的和标准化的 我问这个问题的主要原因是因为我看到的其他答案大部分都引用了timeit,但这给了我负数,所以我改为: import timeit import cProfile

这主要是为了确保我的方法是正确的,但我的基本问题是,如果我需要访问函数,是否值得在函数外部进行检查。我知道,我知道,过早优化,但在许多情况下,将if语句放在函数调用中以确定是否需要运行其余代码,或者将其放在函数调用之前,这是不同的。换言之,这样或那样做都不需要付出任何努力。现在,所有的检查都混合在两者之间,我希望这一切都是好的和标准化的

我问这个问题的主要原因是因为我看到的其他答案大部分都引用了timeit,但这给了我负数,所以我改为:

import timeit
import cProfile

def aaaa(idd):
    return idd

def main():
    #start = timeit.timeit()
    for i in range(9999999):
        a = 5
    #end = timeit.timeit()
    #print("1", end - start)

def main2():
    #start = timeit.timeit()
    for i in range(9999999):
        aaaa(5)
    #end = timeit.timeit()
    #print("2", end - start)

cProfile.run('main()', sort='cumulative')
cProfile.run('main2()', sort='cumulative')
得到这个作为输出

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.310    0.310 {built-in method exec}
        1    0.000    0.000    0.310    0.310 <string>:1(<module>)
        1    0.310    0.310    0.310    0.310 test.py:7(main)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    2.044    2.044 {built-in method exec}
        1    0.000    0.000    2.044    2.044 <string>:1(<module>)
        1    1.522    1.522    2.044    2.044 test.py:14(main2)
  9999999    0.521    0.000    0.521    0.000 test.py:4(aaaa)
ncalls tottime percall cumtime percall文件名:lineno(函数)
1 0.000 0.000 0.310 0.310{内置方法exec}
1    0.000    0.000    0.310    0.310 :1()
1 0.310 0.310 0.310 0.310测试。py:7(主)
1 0.000 0.000 0.000 0.000{方法'disable'的''lsprof.Profiler'对象}
ncalls tottime percall cumtime percall文件名:lineno(函数)
1 0.000 0.000 2.044 2.044{内置方法exec}
1    0.000    0.000    2.044    2.044 :1()
1 1.522 1.522 2.044 2.044测试。py:14(主要2)
99999999 0.521 0.000 0.521 0.000测试。py:4(aaaa)
对我来说,这表明不调用函数需要0.31秒,而调用它需要1.52秒,这几乎慢了5倍。但就像我说的,我得到了时间的负数,所以我想确定它实际上有那么慢


从我收集的资料来看,函数调用如此缓慢的原因是python需要查找以确保函数仍然存在,然后才能运行它或其他什么?难道没有办法让它喜欢……假设一切都还在,这样它就不必做不必要的工作(显然)使它慢了5倍吗?

你在这里比较苹果和梨。一个方法执行简单赋值,另一个调用函数。是的,函数调用会增加开销

您应该在
timeit
期间将其减至最低:

>>> import timeit
>>> timeit.timeit('a = 5')
0.03456282615661621
>>> timeit.timeit('foo()', 'def foo(): a = 5')
0.14389896392822266
现在我们所做的就是添加一个函数调用(
foo
做同样的事情),这样您就可以测量函数调用所花费的额外时间。您不能说这几乎慢了4倍,不,函数调用为1.000.000次迭代增加了0.11秒的开销

如果我们做的事情执行一百万次迭代需要0.5秒,而不是
a=5
,那么将它们移动到一个函数不会让事情花费2秒。现在需要0.61秒,因为函数开销不会增加

函数调用需要操纵堆栈,将本地帧推到堆栈上,创建一个新帧,然后在函数返回时再次清除所有帧

换句话说,将语句移动到函数中会增加一小部分开销,并且移动到该函数中的语句越多,开销占总工作量的百分比就越小。函数永远不会使这些语句本身变慢

Python函数只是存储在变量中的对象;您可以将函数分配给不同的变量,用完全不同的变量替换它们,或者随时删除它们。调用函数时,首先引用存储它们的名称(
foo
),然后调用函数对象(
(参数)
);在动态语言中,这种查找每次都必须发生

在为函数生成的字节码中可以看到:

>>> def foo():
...     pass
... 
>>> def bar():
...     return foo()
... 
>>> import dis
>>> dis.dis(bar)
  2           0 LOAD_GLOBAL              0 (foo)
              3 CALL_FUNCTION            0
              6 RETURN_VALUE        

LOAD\u GLOBAL
opcode在全局名称空间中查找名称(
foo
)(基本上是哈希表查找),并将结果推送到堆栈上
CALL_函数
然后调用堆栈上的任何内容,并将其替换为返回值
RETURN\u VALUE
从函数调用返回,再次将堆栈上最上面的值作为返回值。

这里比较的是苹果和梨。您将赋值与局部变量进行比较,就像调用函数一样,这是两件完全不同的事情。您是如何调用
timeit
得到负数的?我想说,速度慢5倍实际上是快的。我的意思是,调用函数比简单的赋值要复杂得多。您必须1)查看函数是否存在(这是一个缓慢的全局查找)2)打包参数,3)调用本身必须解压缩参数的函数4)执行代码5)返回对象。(事实上,我使用了5个步骤只是一个机会。我不认为这与代码慢了5倍有关)调用
timeit.timeit()
测量了一百万次执行任何操作所需的时间。从
timeit.timeit()
中减去
timeit.timeit()
可能是负数。Martijn:这就是比较在函数中执行某项操作与在函数外部执行某项操作的要点(除非我做错了)。我主要想知道函数调用本身增加的开销是什么。我注释掉了我的timeit方法,以显示我在做什么。尽管它可能会使它们变得更慢,例如,在一个案例中,您将一次又一次地将新值分配给现有变量,而不是在函数范围中创建一个新变量并一次又一次地分配给它。是的,在幕后,它们做的事情并不相同,但从对代码的简单分析来看,它们看起来是一样的。不是所有语言中都会发生对堆栈等的操作吗?不管您是否使用python,都会发生这种代价,对吗?我说的更多的是,我觉得python必须做额外的工作,因为python是动态的,函数可以创建和消失。这就引出了我后面的问题,即是否可以强制python假设它仍然存在,并且没有