Pull to refresh
822.3
OTUS
Цифровые навыки от ведущих экспертов

Модуль dis в Python и свертка констант

Reading time 3 min
Views 9K
Original author: pythontips

Всем привет. Сегодня хотим поделиться еще одним переводом подготовленным в преддверии запуска курса «Web-разработчик на Python». Поехали!



Недавно я очень удивился, когда обнаружил, что


>>> pow(3,89)

работает медленнее, чем


>>> 3**89

Я пытался придумать какое-либо приемлемое объяснение, но не смог. Я засек время выполнения этих двух выражений, используя модуль timeit из Python 3:


$ python3 -m timeit 'pow(3,89)'
500000 loops, best of 5: 688 nsec per loop

$ python3 -m timeit '3**89'
500000 loops, best of 5: 519 nsec per loop

Различие небольшое. Всего 0,1 мкс, но это не давало мне покоя. Если я не могу объяснить что-нибудь в программировании, я начинаю страдать бессонницей


Я нашел ответ с помощью канала Python IRC на Freenode. Причина, по которой pow работает чуть-чуть медленнее заключается в том, что уже в CPython появляется дополнительный шаг загрузки pow из пространства имен. Тогда как при вызове 3**9 такая загрузка не нужна в принципе. Также это значит, что эта разница во времени останется более-менее постоянной, если входные значения будут расти.


Гипотеза подтвердилась:


$ python3 -m timeit 'pow(3,9999)'
5000 loops, best of 5: 58.5 usec per loop

$ python3 -m timeit '3**9999'
5000 loops, best of 5: 57.3 usec per loop

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


Я декомпилировал байт-код выражений, приведенных выше, и получил следующее:


>>> import dis
>>> dis.dis('pow(3,89)')
#  1           0 LOAD_NAME                0 (pow)
#              2 LOAD_CONST               0 (3)
#              4 LOAD_CONST               1 (89)
#              6 CALL_FUNCTION            2
#              8 RETURN_VALUE

>>> dis.dis('3**64')
#  1           0 LOAD_CONST               0 (3433683820292512484657849089281)
#              2 RETURN_VALUE

>>> dis.dis('3**65')
#  1           0 LOAD_CONST               0 (3)
#              2 LOAD_CONST               1 (65)
#              4 BINARY_POWER
#              6 RETURN_VALUE

Вы можете почитать о том, как правильно понимать выходные данные dis.dis, обратившись к этому ответу на Stackoverflow.


Хорошо, вернемся к коду. В декомпиляции pow есть смысл. Байт-код загружает pow из пространства имен, загружает в регистры 3 и 89, и, наконец, вызывает функцию pow. Но почему выходные данные следующих двух декомпиляций отличаются друг от друга? Ведь все, что мы изменили, это значение показателя степени с 64 на 65!


Этот вопрос познакомил меня еще с одним новым понятием, которое называется «свертка констант». Оно означает, что когда у нас есть константное выражение, Python вычисляет его значение на этапе компиляции, так что когда вы запустите программу, ее работа не займет много времени, поскольку Python использует уже вычисленное значение. Взгляните на это:


def one_plue_one():
     return 1+1

# --vs--

def one_plue_one():
     return 2

Python компилирует первую функцию во вторую и использует ее при запуске кода. Неплохо, да?


Так почему свертка констант работает для 3**64, но не для 3**65? Что ж, я не знаю. Вероятно, это как-то связано с ограничением количества степеней, предварительно вычисленных системой в памяти. Я могу ошибаться. Следующий шаг, который я планирую предпринять, это порыться в исходном коде Python в свободное время и попытаться понять, что происходит. Я все еще ищу ответ на свой вопрос, поэтому, если у вас есть какие-то идеи, делитесь ими в комментариях.


Мне хочется, чтобы из этого поста вы почерпнули вдохновение на поиск решения своих вопросов самостоятельно. Вы никогда не знаете, куда вас приведут ответы. В конечном итоге вы можете узнать что-то совершенно новое, как случилось со мной. Надеюсь, в вас еще горит пламя любопытства!


Замечали похожие штуки? Ждём ваших комментариев!

Tags:
Hubs:
+12
Comments 4
Comments Comments 4

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS