2016-07-15 1 views
-1

Я запускаю 2 функции. Оба они имеют for-loops для выполнения инструкций. Обе функции выполняют одну и ту же задачу, но она занимает гораздо больше времени.Разница во времени выполнения сценария для кода в цикле против кода в вызываемой функции

Функция 1 выполняет и является автономной, выполняет TaskA.

f1: 
    For x in X: 
    do task a 

Функция 2 исполняет и вызовы функций 3. Функция 3 выполняет Таска

f2: 
    For x in X: 
    call function 3 
f3: 
    do task a 

Почему функция 2 обычно занимает в 10 раз до тех пор, чтобы выполнить как функция 1?

EDIT: Предыдущие фразы смутили людей.

+3

Ну, очевидно, этот псевдокод медленный, потому что вы используете ужасный интерпретатор псевдокода ... Вы считали, что вместо этого вы отправляете ** фактический ** код? –

+0

Вы даете нам код, где вызов функции буквально является единственной разницей. Единственный вывод, к которому мы можем прийти, это «Функциональные вызовы медленные». Тот факт, что вызовы функций не являются (заметно) медленными в реальности, показывает, что вы фактически не опубликовали соответствующий фрагмент кода. Я предлагаю вам попробовать ['cProfile.run ('f2()')'] (https://docs.python.org/2/library/profile.html) –

ответ

1

Другим фактором может быть «подготовка»/настройка, выполняемая до того, как вызывается TaskA. Возможно, что в f1 вы сделали это один раз перед циклом for, а затем это делается в f3, поэтому он вызывается для каждого x in X от f2, а не только один раз в начале. Без реального кода трудно сказать.

Что касается потенциальной сложности вызова f3 за каждые x, маловероятно, что это является причиной 10-кратной медлительности.

Только в упрощенном примере с pass мы видим это поведение. Давайте эти 3 плохие версии f1, f2 и f3:

>>> def f1(): 
... for x in X: 
...  pass 
... 
>>> def f2(): 
... for x in X: 
...  f3() 
... 
>>> def f3(): 
... pass 
... 

Используя dis, вот что байт-код выглядит для f1:

>>> dis.dis(f1) 
    2   0 SETUP_LOOP    14 (to 17) 
       3 LOAD_GLOBAL    0 (X) 
       6 GET_ITER 
     >> 7 FOR_ITER     6 (to 16) 
      10 STORE_FAST    0 (x) 

    3   13 JUMP_ABSOLUTE   7 
     >> 16 POP_BLOCK 
     >> 17 LOAD_CONST    0 (None) 
      20 RETURN_VALUE 

... против f2:

>>> dis.dis(f2) 
    2   0 SETUP_LOOP    21 (to 24) 
       3 LOAD_GLOBAL    0 (X) 
       6 GET_ITER 
     >> 7 FOR_ITER    13 (to 23) 
      10 STORE_FAST    0 (x) 

    3   13 LOAD_GLOBAL    1 (f3) 
      16 CALL_FUNCTION   0 
      19 POP_TOP 
      20 JUMP_ABSOLUTE   7 
     >> 23 POP_BLOCK 
     >> 24 LOAD_CONST    0 (None) 
      27 RETURN_VALUE 

Те же, что и у CALL_FUNCTION и POP_TOP. Тем не менее, они очень разные с timeit:

>>> X = range(1000) # [0, 1, 2, ...999] 
>>> 
>>> import timeit 
>>> timeit.timeit(f1) 
10.290941975496747 
>>> timeit.timeit(f2) 
81.18860785875617 
>>> 

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

Теперь, если вы на самом деле сделать что-то в задаче, как говорят x * x тогда вы увидите разницу времени/производительность между двумя становится меньше:

>>> def f1(): 
... for x in X: 
...  _ = x*x 
... 
>>> def f2(): 
... for x in X: 
...  _ = f3(x) # didn't pass in `x` to `f3` in the previous example 
... 
>>> def f3(x): 
... return x*x 
... 
>>> timeit.timeit(f1) 
38.76545268807092 
>>> timeit.timeit(f2) 
113.72242594670047 
>>> 

Теперь это только 2.9x время.Это не вызов функции, который вызывает медленность (да, есть некоторые накладные расходы), но и то, что вы делаете в этой функции vs pass, что имеет значение для общего времени.

Если заменить _ = x * x с print x * x в обоих местах, что вполне «медленные», и только с X = range(5):

>>> timeit.timeit(f1, number=10000) 
3.640433839719143 
>>> timeit.timeit(f2, number=10000) 
3.6921612171574765 

А теперь гораздо меньше различий в их исполнении.

Так что сделайте реальную проверку с помощью реального кода, а не просто с помощью простого анализа псевдокода. Пустые вызовы могут появляться быстрее, но эти накладные расходы очень малы по сравнению с более медленными вещами, что делает код в функциях.

+0

Я очень ценю ваш углубленный анализ. Это то, что я искал. Извините, если это вообще слишком много, чтобы спросить на этой доске, я не знал. – Angus

+0

Проблема в том, что задавать конкретный вопрос, такой как ваш, без реального кода, действительно сложно ответить. Поэтому я составил случайный пример, который может объяснить, почему вы видели такую ​​большую разницу в производительности. – aneroid