Профилирование системы с помощью многократно используемых декораторов - PullRequest
10 голосов
/ 22 февраля 2012

В нашей кодовой базе есть несколько широко используемых декораторов.

Когда я создаю профиль времени выполнения, большая часть графика вызовов выглядит как песочные часы; многие функции вызывают одну функцию (декоратор), которая затем вызывает много функций. Это менее полезный профиль, чем хотелось бы.

Есть ли способ исправить эту ситуацию? Удаление декоратора не вариант; обеспечивает необходимую функциональность.

Мы рассмотрели ручное удаление декоратора из данных cProfile после факта, но это не представляется возможным, потому что данные суммируются в отношения caller-> callee, которые разрушают отношение caller-> decorator-> callee .

Ответы [ 2 ]

6 голосов
/ 22 февраля 2012

Используя что-то вроде библиотеки new (или types в Python 2.6+), вы можете теоретически динамически создать объект кода, а затем объект функции на основе этогоКодовый объект, имеющий встроенное имя, которое варьировалось вместе с функцией, которую вы оборачивали.

Это позволило бы вам манипулировать вещами настолько глубоко, как <func>.__code__.co_name (который обычно доступен только для чтения).


import functools
import types

def metadec(func):

    @functools.wraps(func)
    def wrapper(*args, **kwargs):   
        # do stuff
        return func(*args, **kwargs)

    c = wrapper.func_code
    fname = "%s__%s" % (func.__name__, wrapper.__name__)

    code = types.CodeType(
                c.co_argcount, 
                c.co_nlocals,
                c.co_stacksize,
                c.co_flags,  
                c.co_code,        
                c.co_consts,         
                c.co_names,
                c.co_varnames,
                c.co_filename,
                fname, # change the name
                c.co_firstlineno,
                c.co_lnotab,
                c.co_freevars,
                c.co_cellvars,
            )

    return types.FunctionType(
            code, # Use our updated code object
            wrapper.func_globals,
            fname, # Use the updated name
            wrapper.func_defaults,
            wrapper.func_closure,
        )

(functools.wraps все еще используется здесь для обеспечения возможности прохода таких вещей, как строки документов, имена модулей и т. Д.)


In [1]: from metadec import metadec

In [2]: @metadec
   ...: def foobar(x):
   ...:     print(x)
   ...:     
   ...:     

In [3]: foobar.__name__
Out[3]: 'foobar__wrapper'

In [4]: foobar(1)
1
5 голосов
/ 22 февраля 2012

Я собираюсь догадаться, что не ваш декоратор загромождает ваше профилирование, а скорее функция оболочки , созданная декоратором. И это происходит потому, что все функции-оболочки имеют одинаковые имена. Чтобы решить эту проблему, просто попросите декоратора изменить имя функции-оболочки.

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    wrapper.__name__ += "_" + func.__name__
    return wrapper

Вы также можете использовать functools.wraps(), но тогда имя функции-оболочки будет совпадать с именем функции, которую она оборачивает. Я думаю, это было бы хорошо для профилирования.

Теперь объект кода функции также имеет имя. Python не хранит ссылки на функции в стеке, только на объекты кода, поэтому, если профилировщик получает имя функции-оболочки из фрейма стека, он получит это имя. Оболочки, определенные обычным способом, совместно используют объект кода (даже если объект функции отличается), если вы явно не перестраиваете объект кода и объект функции для каждой функции оболочки. Это немного больше работы и очень специфично для CPython (может быть, даже для конкретной версии). Но вот как вы можете это сделать:

from types import FunctionType, CodeType    

def decorator(func):

    def wrapper(*args):
        print "enter func", func.__name__
        return func(*args)

    name = wrapper.__name__ + "_" + func.__name__

    func_code = wrapper.func_code
    new_code  = CodeType(
            func_code.co_argcount, func_code.co_nlocals, func_code.co_stacksize,
            func_code.co_flags, func_code.co_code, func_code.co_consts,
            func_code.co_names, func_code.co_varnames, func_code.co_filename,
            name, func_code.co_firstlineno, func_code.co_lnotab,
            func_code.co_freevars, func_code.co_cellvars)
    wrapper   = FunctionType(
            new_code, wrapper.func_globals, name, wrapper.func_defaults,
            wrapper.func_closure)

    return wrapper

И имя функции, и имя объекта кода установлены здесь на wrapper_originalfuncname, и поэтому они должны учитываться отдельно от встроенной функции в профилировщике. Вы можете легко установить для них только имя исходной функции, чтобы вместо этого время выполнения было заменено исходной функцией.

...