简体   繁体   English

Python函数调用非常慢

[英]Python Function calls are really slow

This is mostly to make sure my methodology is correct, but my basic question was is it worth it to check outside of a function if I need to access the function at all. 这主要是为了确保我的方法是正确的,但我的基本问题是,如果我需要访问函数,那么检查函数外部是否值得。 I know, I know, premature optimization, but in many cases, its the difference between putting an if statement inside the function call to determine whether I need to run the rest of the code, or putting it before the function call. 我知道,我知道,过早优化,但在很多情况下,它在函数调用中放置一个if语句以确定是否需要运行其余代码,或者将它放在函数调用之前。 In other words, it takes no effort to do it one way or the other. 换句话说,它不会以任何方式做到这一点。 Right now, all the checks are mixed between both, and I'd like the get it all nice and standardized. 现在,所有的检查都在两者之间混合,我想让它变得更加美观和标准化。

The main reason I asked is because the other answers I saw mostly referenced timeit, but that gave me negative numbers, so I switched to this: 我问的主要原因是因为我看到的其他答案主要是参考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')

and got this for output 得到这个输出

   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)

To me that shows that not calling the function is .31 seconds, and calling it takes 1.52 seconds, which is almost 5 times slower. 对我来说,显示不调用该函数是.31秒,并且调用它需要1.52秒,这几乎慢了5倍。 But like I said, I got negative numbers with timeit, so I want to make sure its actually that slow. 但就像我说的那样,我得到了带有timeit的负数,所以我想确保它实际上慢。

Also from what I gather, the reason function calls are so slow is because python needs to look up to make sure the function still exists before it can run it or something? 另外,从我收集的内容来看,函数调用的原因是如此缓慢是因为python需要查找以确保函数在运行之前仍然存在或其他什么? Isn't there any way to just tell it to like...assume that everything is still there so that it doesn't have to do unnecessary work that (apparently) slows it down 5x? 是不是有任何方法只是告诉它喜欢...假设一切仍然存在,以便它不必做不必要的工作(显然)减慢它5倍?

You are comparing apples and pears here. 你在这里比较苹果和梨。 One method does simple assignment, the other calls a function. 一种方法执行简单的赋值,另一种方法调用函数。 Yes, function calls will add overhead. 是的,函数调用增加开销。

You should strip this down to the bare minimum for timeit : 你应该剥离下来到最低限度,为timeit

>>> import timeit
>>> timeit.timeit('a = 5')
0.03456282615661621
>>> timeit.timeit('foo()', 'def foo(): a = 5')
0.14389896392822266

Now all we did was add a function call ( foo does the same thing), so you can measure the extra time a function call takes. 现在我们所做的就是添加一个函数调用( foo做同样的事情),这样你就可以测量函数调用所需的额外时间。 You cannot state that this is nearly 4 times slower, no, the function call adds a 0.11 second overhead for 1.000.000 iterations. 你不能说这差不多慢了4倍,不,函数调用为1.000.000次迭代增加了0.11秒的开销。

If instead of a = 5 we do something that takes 0.5 seconds to execute one million iterations, moving them to a function won't make things take 2 seconds. 如果不是a = 5我们做了0.5秒执行一百万次迭代的事情,将它们移动到一个函数将不会花费2秒。 It'll now take 0.61 seconds because the function overhead doesn't grow. 它现在需要0.61秒,因为函数开销不会增长。

A function call needs to manipulate the stack, pushing the local frame onto it, creating a new frame, then clear it all up again when the function returns. 函数调用需要操作堆栈,将本地帧推到它上,创建一个新帧,然后在函数返回时再次清除它。

In other words, moving statements to a function adds a small overhead, and the more statements you move to that function, the smaller the overhead becomes as a percentage of the total work done. 换句话说,将语句移动到函数会增加很小的开销,并且移动到该函数的语句越多,开销就越小,占完成工作总量的百分比。 A function never makes those statements themselves slower. 函数永远不会使这些语句本身变慢。

A Python function is just an object stored in a variable; Python函数只是存储在变量中的对象; you can assign functions to a different variable, replace them with something completely different, or delete them at any time. 您可以将函数分配给不同的变量,将它们替换为完全不同的变量,或者随时删除它们。 When you invoke a function, you first reference the name by which they are stored ( foo ) and then invoke the function object ( (arguments) ); 当你调用一个函数时,首先引用它们存储的名称( foo ),然后调用函数对象( (arguments) ); that lookup has to happen every single time in a dynamic language. 查找必须每次都以动态语言进行。

You can see this in the bytecode generated for a function: 您可以在为函数生成的字节码中看到这一点:

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

The LOAD_GLOBAL opcode looks up the name ( foo ) in the global namespace (basically a hash table lookup), and pushes the result onto the stack. LOAD_GLOBAL操作码在全局命名空间中查找名称( foo )(基本上是哈希表查找),并将结果推送到堆栈。 CALL_FUNCTION then invokes whatever is on the stack, replacing it with the return value. 然后CALL_FUNCTION调用堆栈上的任何内容,将其替换为返回值。 RETURN_VALUE returns from a function call, again taking whatever is topmost on the stack as the return value. RETURN_VALUE从函数调用返回,再次将堆栈中最顶层的任何内容作为返回值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM