Используйте функцию декоратора для сравнения времени выполнения - PullRequest
0 голосов
/ 18 марта 2020

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

compare_functions(f1, f2) 

, которая сравнивает время выполнения двух функций f1 и f2. Для начала я разработал этот декоратор:

def clean_exec_time(fnc):
    def wrapper(*args):
        t0 = time.perf_counter()
        fnc(*args)
        t1 = time.perf_counter()
        print(t1-t0)
return wrapper

, который можно идеально использовать таким образом:

@clean_exec_time
def print_msg(msg):
    print(msg)

Такой, что

print_msg('Hi') 
# outputs:
# Hi
# 3.0109e-05

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

@clean_exec_time
def compare_functions(f1, f2): 
    a = 2 #not sure what to put here

Теперь я вижу следующее:

compare_functions(print_msg('Hi'), print_msg('See ya'))
# outputs
# Hi
# 3.0793e-05
# See ya
# 1.1291e-05
# 6.5010e-05

У меня есть несколько вопросов:

  1. Почему после «Увидимся» напечатаны 2 раза выполнения ?
  2. Что мне нужно изменить, чтобы увидеть напечатанное значение c, которое обозначает количество раз, когда f1 быстрее / медленнее, чем f2.
  3. Странно не вкладывать в мою функцию ничего значащего Compare_functions. Есть ли элегантный способ справиться с этим?

Ответы [ 3 ]

1 голос
/ 20 марта 2020

compare_functions - это неправильная функция для декорирования, потому что вы хотите, чтобы каждая из функций была передана в compare_functions, а не compare_functions сама.

Я нахожу ее более полезно использовать временный код с помощью диспетчера контекста.

import time
import contextlib


class TimeResult:
    def __init__(self):
        self._start = None
        self._stop = None

    def start(self):
        self._start = time.perf_counter()

    def stop(self):
        self._stop = time.perf_counter()

    @property
    def result(self):
        if self._start is None or self._stop is None:
            return None
        return self._stop - self._start

Это простой класс, свойство result которого равно None до тех пор, пока вы не вызовете оба метода start и stop ( желательно в таком порядке)

@contextlib.contextmanager
def timeit():
    t = TimeResult()
    t.start()
    yield t
    t.stop()

Это создает менеджер контекста, который выдает объект TimeResult, чьи методы start и stop вызываются при входе и при выходе из оператора with соответственно.

def compare_functions(f1, f2): 
    with timeit() as t1:
        f1()

    print(f'Function 1 took {t1.result} seconds')

    with timeit() as t2:
        f2()

    print(f'Function 2 took {t2.result} seconds')

compare_functions принимает две функции для вызова, и каждый раз из них менеджер контекста timeit. Когда каждая функция завершается, сообщается прошедшее время.

Затем

def print_msg(msg):
    print(msg)

compare_functions(lambda: print_msg("hi"), lambda: print_msg("see ya"))

принимает две функции для вызова и времени.

Кроме того, вы можете передать функции времени и аргументы отдельно, и пусть compare_functions объединит их.

def compare_functions(f_and_args_1, f_and_args_2):
    f1, *args1 = f_and_args1
    f2, *args2 = f_and_args2

    with timeit() as t1:
        f1(*args1)

    print(f'{f1.__name__} took {t1.result} seconds')

    with timeit() as t2:
        f2(*args2)

    print(f'{f2.__name__} took {t2.result} seconds')



compare_functions((print_msg, "hi"), (print_msg, "see ya"))
1 голос
/ 18 марта 2020

Как уже говорилось, одна возможность - использовать лямбда-выражения в качестве аргументов:

def compare_time(f1, f2):
    t0 = time.perf_counter()
    f1()
    t1 = time.perf_counter()
    f2()
    t2 = time.perf_counter()
    print(f"Time diff is {(t1 - t0) - (t2 - t1)} s")

compare_time(lambda: print_msg('Hi'), lambda: print_msg('See ya'))

Это дает:

Hi
See ya
Time diff is 1.9200000000010875e-05 s

К сожалению, лямбда-функции не имеют информации о вызываемой функции. Если вам это нужно, вот хакерское решение, которое принимает строки вызовов функций:

def compare_time1(f1, f2):
    t0 = time.perf_counter()
    eval(f1)
    t1 = time.perf_counter()
    eval(f2)
    t2 = time.perf_counter()
    print(f"Time diff between {f1} and {f2} is {(t1 - t0) - (t2 - t1)} s")

compare_time1("print_msg('Hi')", "print_msg('See ya')")

Это дает:

Hi
See ya
Time diff between print_msg('Hi') and print_msg('See ya') is 3.0000000000002247e-05 s

Другая возможность состоит в разделении имен функций и аргументов - не такой смешной, но и не очень удобный:

def compare_time2(f1, args1, f2, args2):
    t0 = time.perf_counter()
    f1(*args1)
    t1 = time.perf_counter()
    f2(*args2)
    t2 = time.perf_counter()
    print(f"Time diff between {f1.__name__}({str(args1)[1:-1]}) and"
          f" {f2.__name__}({str(args2)[1:-1]}) is {(t1 - t0) - (t2 - t1)} s")

compare_time2(print_msg, ['Hi'], print_msg, ['See ya'])

Это дает:

Hi
See ya
Time diff between print_msg('Hi') and print_msg('See ya') is 8.000000000091267e-07 s
0 голосов
/ 19 марта 2020

Для дальнейшего использования, это то, что я сделал ( без декораторов ).

def compare_functions(f1, f2, reps=1):
    text_trap = io.StringIO()    # 
    sys.stdout = text_trap       # These two lines avoid print statements 
    fnc1 = get_func(f1)
    fnc2 = get_func(f2)
    c1 = get_exec_time(fnc1, reps)
    c2 = get_exec_time(fnc2, reps)
    sys.stdout = sys.__stdout__   # turn print statement back on
    if c1 > c2:
        print('Function {0} is {1:.2f} times faster than function {2}'.format(1, c1/c2, 2))
    if c2 > c1:
        print('Function {0} is {1:.2f} times faster than function {2}'.format(2, c2/c1, 1))

Где

def get_func(fnc):
    if not callable(fnc):
        return lambda: fnc
    else:
        return fnc

def get_system_speed():           #used as benchmark
    t0 = time.perf_counter()
    do_calc()                     #arbritrary function
    return time.perf_counter()-t0

def get_exec_time(fnc, reps=1):
    ex_times = []
    for i in range(reps):
        c = get_system_speed()
        t0 = time.perf_counter()
        fnc()
        ex_times.append((time.perf_counter()-t0)/c)
return np.average(ex_times)

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

compare_functions(lambda: print_msg('hi'), lambda: print_msg('hi'), reps=10000)
# outputs
# Function 2 is 1.00 times faster than function 1

Спасибо за помощь и предложения!

...