Чи оптимізує Python змінну, яка використовується лише як повернене значення?


106

Чи є якась остаточна різниця між наступними двома фрагментами коду? Перший присвоює значення змінній функції, а потім повертає цю змінну. Друга функція просто повертає значення безпосередньо.

Чи перетворює Python їх на еквівалентний байт-код? Чи один з них швидший?

Випадок 1 :

def func():
    a = 42
    return a

Випадок 2 :

def func():
    return 42

5
Якщо ви використовуєте dis.dis(..)обидва, ви бачите, що є різниця , так що так. Але в більшості реальних застосувань рентабельність цього порівняно із затримкою обробки у функції не така вже й велика.
Віллем Ван Онсем

4
Є дві можливості: (а) Ви будете викликати цю функцію багато (тобто принаймні мільйон) разів у тісному циклі. У цьому випадку ви взагалі не повинні викликати функцію Python, а натомість слід векторизувати цикл, використовуючи щось на зразок бібліотеки numpy. (b) Ви не збираєтеся викликати цю функцію так багато разів. У цьому випадку різниця в швидкості між цими функціями занадто мала, щоб варто було б турбуватися.
Артур Такка

Відповіді:


138

Ні, це не так .

Компіляція до байтового коду CPython передається лише через невеликий оптимізатор видовища, який призначений для виконання лише базових оптимізацій ( докладнішу інформацію про ці оптимізації див. У тестовому наборі test_peepholer.py ).

Щоб ознайомитись з тим, що насправді відбуватиметься, використовуйте dis*, щоб переглянути створені інструкції. Для першої функції, що містить призначення:

from dis import dis
dis(func)
  2           0 LOAD_CONST               1 (42)
              2 STORE_FAST               0 (a)

  3           4 LOAD_FAST                0 (a)
              6 RETURN_VALUE

Хоча для другої функції:

dis(func2)
  2           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

У першій застосовуються ще дві (швидкі) інструкції: STORE_FASTі LOAD_FAST. Вони роблять швидке зберігання та захоплення значення в fastlocalsмасиві поточного кадру виконання. Потім в обох випадках RETURN_VALUEвиконується а. Отже, другий стає дещо швидшим за рахунок меншої кількості команд, необхідних для виконання.

Загалом, майте на увазі, що компілятор CPython є консервативним в оптимізаціях, які він виконує. Він не є і не намагається бути таким розумним, як інші компілятори (з якими, як правило, також є набагато більше інформації для роботи). Основна мета дизайну, окрім очевидності правильної, полягає в тому, щоб: а) зберігати її просто і б) бути максимально швидкою при складанні цих матеріалів, щоб ви навіть не помітили, що існує фаза компіляції.

Зрештою, ви не повинні турбувати себе з такими дрібними проблемами, як ця. Користь у швидкості невелика, постійна і, карликована накладними, спричиненими тим, що Python інтерпретується.

* disце невеликий модуль Python, який розбирає ваш код, ви можете використовувати його, щоб побачити байт-код Python, який буде виконувати VM.

Примітка: Як також зазначалося в коментарі @Jorn Vernee, це специфічно для CPython реалізації Python. Інші реалізації можуть зробити більш агресивні оптимізації, якщо вони цього хочуть, CPython цього не робить.


11
Це не пітон (c ++), тому я не знаю, як це працює під кришкою, але чи не слід перший випадок оптимізувати до другого випадку? Гідний компілятор C ++ зробив би цю оптимізацію.
NathanOliver

7
@NathanOliver це насправді не так, Python зробить так, як сказано тут, навіть не намагаючись грати на ньому розумно.
Дімітріс Фасаракіс Хілліард

80
Той факт, що цілком розумна та розумна здогадка @ NathanOliver при відповіді на це питання є абсолютно неправильним, на мій погляд, доказом того, що це не "самоясне", "нісенітниця", "дурне" питання, на яке можна відповісти. "задумавшись", як нам би повірили TigerhawkT3. Це дійсне, цікаве питання, на яке я не був впевнений у відповіді, незважаючи на те, що протягом багатьох років був професійним програмістом Python.
Марк Амері

Компілятор Python в кращому випадку "консервативний", а не "дуже консервативний". Основна мета дизайну - не бути "максимально швидким ... так що ви навіть не помічаєте, що існує фаза компіляції". Це другорядне значення, після "нехай це буде просто". Функція з великими константами, такими як "1 << (2 ** 34)" та "b'x '* (2 ** 32)", займає кілька секунд, щоб скласти та генерувати константи розміром GB, навіть якщо функція ніколи бігати. Великий рядок навіть буде відкинутий компілятором. Запропоновані виправлення для цих випадків були відхилені, оскільки вони зробили б компілятор занадто складним.
Ендрю Далке

@AndrewDalke дякую за коментарі інсайдерів про це, я переробив формулювання для вирішення проблем, які ви вказали.
Димитріс Фасаракіс Хілліард

3

Обидва в основному однакові, за винятком того, що в першому випадку об'єкт 42просто присвоюється змінній, названій aабо, іншими словами, імена (тобто a) посилаються на значення (тобто 42). Він не робить жодного завдання технічно, в тому сенсі, що ніколи не копіює жодних даних.

Під час returning, ця названа прив'язка aповертається в першому випадку, тоді як об'єкт 42повертається у другому.

Для більш детального ознайомлення дивіться цю чудову статтю Неда Батчелдера

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.