UFO ET IT

Python에서 함수를 중첩 할 때 오버 헤드가 있습니까?

ufoet 2020. 12. 30. 08:31
반응형

Python에서 함수를 중첩 할 때 오버 헤드가 있습니까?


파이썬에서 부모 함수 내에 자식 함수가있는 경우 부모 함수가 호출 될 때마다 자식 함수가 "초기화"(생성)됩니까? 함수를 다른 함수에 중첩하는 것과 관련된 오버 헤드가 있습니까?


예, 매번 새 개체가 생성됩니다. 타이트한 루프에 있지 않으면 문제가되지 않을 것입니다. 프로파일 링은 문제인지 알려줍니다.

In [80]: def foo():
   ....:     def bar():
   ....:         pass
   ....:     return bar
   ....: 

In [81]: id(foo())
Out[81]: 29654024

In [82]: id(foo())
Out[82]: 29651384

코드 개체는 부분적으로 오버 헤드가 없도록 미리 컴파일됩니다. 함수 객체는 모든 호출에서 빌드되며 함수 이름을 코드 객체에 바인딩하고 기본 변수 등을 기록합니다.

요약 : 무료가 아닙니다.

>>> from dis import dis
>>> def foo():
        def bar():
                pass
        return bar

>>> dis(foo)
  2           0 LOAD_CONST               1 (<code object bar at 0x1017e2b30, file "<pyshell#5>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (bar)

  4           9 LOAD_FAST                0 (bar)
             12 RETURN_VALUE 

영향이 있지만 대부분의 상황에서 너무 작아서 걱정할 필요가 없습니다. 대부분의 사소하지 않은 애플리케이션에는 이미 이보다 훨씬 큰 영향을 미치는 성능 병목 현상이있을 수 있습니다. 대신 코드의 가독성과 재사용 가능성에 대해 걱정하십시오.

루프를 통해 매번 함수를 재정의하는 성능을 대신 미리 정의 된 함수를 재사용하는 것과 비교하는 코드입니다.

import gc
from datetime import datetime

class StopWatch:
     def __init__(self, name):
         self.name = name

     def __enter__(self):
         gc.collect()
         self.start = datetime.now()

     def __exit__(self, type, value, traceback):
         elapsed = datetime.now()-self.start
         print '** Test "%s" took %s **' % (self.name, elapsed)

def foo():
     def bar():
          pass
     return bar

def bar2():
    pass

def foo2():
    return bar2

num_iterations = 1000000

with StopWatch('FunctionDefinedEachTime') as sw:
    result_foo = [foo() for i in range(num_iterations)]

with StopWatch('FunctionDefinedOnce') as sw:
    result_foo2 = [foo2() for i in range(num_iterations)]

OS X Lion을 실행하는 Macbook Air의 Python 2.7에서 이것을 실행하면 다음과 같은 결과가 나타납니다.

** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **

나도 이것에 대해 궁금해서 이것이 얼마나 많은 오버 헤드를 초래 했는지 알아 내기로 결정했습니다 . TL; DR, 대답은 많지 않습니다.

Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from timeit import timeit
>>> def subfunc():
...     pass
... 
>>> def no_inner():
...     return subfunc()
... 
>>> def with_inner():
...     def s():
...         pass
...     return s()
... 
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__     import no_inner', number=1)
0.22971350199986773
>>> timeit('[with_inner() for _ in range(1000000)]', setup='from __main__ import with_inner', number=1)
0.2847519510000893

My instinct was to look at percents (with_inner is 24% slower), but that number is misleading in this case, since we'll never actually just return the value of an inner function from an outer function, especially with functions that don't actually do anything.
After making that mistake, I decided to compare it to other common things, to see when this does and does not matter:

    >>> def no_inner():
    ...     a = {}
    ...     return subfunc()
    ... 
    >>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
    0.3099582109998664

Looking at this, we can see that it takes less time than creating an empty dict (the fast way), so if you're doing anything non-trivial, this probably does not matter at all.


The other answers are great and really answer the question well. I wanted to add that most inner functions can be avoided in python using for loops, generating functions, etc.

Consider the following Example:

def foo():
    # I need to execute some function on two sets of arguments:
    argSet1 = (arg1, arg2, arg3, arg4)
    argSet2 = (arg1, arg2, arg3, arg4)

    # A Function could be executed on each set of args
    def bar(arg1, arg2, arg3, arg4):
        return (arg1 + arg2 + arg3 + arg4)

    total =  bar(argSet1)
    total += bar(argSet2)

    # Or a loop could be used on the argument sets
    total = 0
    for arg1, arg2, arg3, arg4 in [argSet1, argSet2]:
        total += arg1 + arg2 + arg3 + arg4

This example is a little goofy, but I hope you can see my point nonetheless. Inner functions are often not needed.


Yes. This enables closures, as well as function factories.

A closure causes the inner function to remember the state of its environment when called.

def generate_power(number):

    # Define the inner function ...
    def nth_power(power):
        return number ** power

    return nth_power

Example

>>> raise_two = generate_power(2)
>>> raise_three = generate_power(3)

>>> print(raise_two(3))
8
>>> print(raise_three(5))
243
"""

ReferenceURL : https://stackoverflow.com/questions/7839632/is-there-an-overhead-when-nesting-functions-in-python

반응형