Является ли разделение на две строки таким же эффективным? - PullRequest
9 голосов
/ 24 апреля 2019

С точки зрения эффективности времени выполнения в python они одинаково эффективны?

x = foo()
x = bar(x)

VS

x = bar(foo())

У меня есть более сложная проблема, которая, по сути, сводится к этому вопросу: очевидно, с точки зрения длины кода вторая более эффективна, но также лучше ли время выполнения? Если нет, то почему бы и нет?

Ответы [ 2 ]

5 голосов
/ 24 апреля 2019

Вот сравнение:

Первый случай :

%%timeit
def foo():
    return "foo"

def bar(text):
    return text + "bar"

def test():
    x = foo()
    y = bar(x)
    return y

test()
#Output:
'foobar'
529 ns ± 114 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Второй случай :

%%timeit

def foo():
    return "foo"

def bar(text):
    return text + "bar"

def test():   
    x = bar(foo())
    return x

test()
#Output:
'foobar'
447 ns ± 34.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Ноэто просто сравнение, запускающее %% timeit один раз для каждого случая.Ниже приведены времена для 20 итераций (время в нс) для каждого случая:

df = pd.DataFrame({'First Case(time in ns)': [623,828,634,668,715,659,703,687,614,623,697,634,686,822,671,894,752,742,721,742], 
               'Second Case(time in ns)': [901,786,686,670,677,683,685,638,628,670,695,657,698,707,726,796,868,703,609,852]})

df.plot(kind='density', figsize=(8,8))

enter image description here

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

В первом случае вычисляются два выражения: первое выражение присваивает возвращаемое значение сначала от foo() до x, а затем второе выражение вызывает bar() для этого значения.Это добавляет некоторые накладные расходы.Во втором случае вычисляется только одно выражение, вызывающее обе функции одновременно и возвращающее значение.

2 голосов
/ 24 апреля 2019

Имеет значение крошечный бит, но не имеет смысла. тест Аманба рассчитал время определения функций только в одном из тестов, и поэтому в первом тесте пришлось выполнять больше работы, искажая результаты. При правильном тестировании результаты отличаются только наименьшими полями. Используя ту же магию ipython %%timeit (IPython версии 7.3.0, CPython версии 3.7.2 для Linux x86-64), но удалив определение функций из тестов для каждого цикла:

>>> def foo():
...     return "foo"
... def bar(text):
...     return text + "bar"
... def inline():
...     x = bar(foo())
...     return x
... def outofline():
...     x = foo()
...     x = bar(x)
...     return x
...

>>> %%timeit -r5 test = inline
... test()
...
...
332 ns ± 1.01 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)


>>> %%timeit -r5 test = outofline
... test()
...
...
341 ns ± 5.62 ns per loop (mean ± std. dev. of 5 runs, 1000000 loops each)

Код inline был быстрее, но разница была ниже 10 нс / 3%. Дальнейшее наклонение (чтобы сделать тело просто return bar(foo())) экономит крошечный немного больше, но опять же, это довольно бессмысленно.

Это то, что вы ожидаете тоже; Хранение и загрузка локальных имен функций - это самое дешевое, что может сделать интерпретатор CPython, единственное различие между функциями заключается в том, что outofline требует дополнительных STORE_FAST и LOAD_FAST (одна за другой) и эти инструкции реализованы внутри как не что иное, как присвоение и считывание из определенного интервала времени компиляции в массиве C, плюс одно целочисленное приращение для настройки счетчиков ссылок. Вы платите за издержки интерпретатора CPython, необходимые для каждого байтового кода, но стоимость реальной работы тривиальна.

Суть: Не беспокойтесь о скорости, напишите любую версию кода, которая была бы более удобочитаемой / поддерживаемой. В этом случае все имена являются мусором, но если вывод из foo может быть присвоено полезное имя, затем передано bar, чьему выводу присвоено другое полезное имя, и без этих имен связь между foo и bar неочевидна, не встроена. Если связь очевидна, и вывод foo не получает выгоды от имени, вставьте его в строку. Избегать магазинов и нагрузок от локальных переменных - это самая микро микрооптимизация; это не будет причиной значительного снижения производительности практически в любом сценарии, поэтому не основывайте на этом решения по проектированию кода.

...