Здесь есть две проблемы. Первый шаг — выяснить, какая часть выполняется быстрее: оператор импорта или вызов.
Итак, давайте так:
$ python -mtimeit 'import math'
1000000 loops, best of 3: 0.555 usec per loop
$ python -mtimeit 'from math import sqrt'
1000000 loops, best of 3: 1.22 usec per loop
$ python -mtimeit -s 'from math import sqrt' 'sqrt(10)'
10000000 loops, best of 3: 0.0879 usec per loop
$ python -mtimeit -s 'import math' 'math.sqrt(10)'
10000000 loops, best of 3: 0.122 usec per loop
(Это с 64-разрядной версией Apple CPython 2.7.2 на OS X 10.6.4 на моем ноутбуке. Но python.org 3.4 dev на том же ноутбуке и 3.3.1 на Linux дают примерно одинаковые результаты. С PyPy более интеллектуальное кэширование делает невозможным тестирование, так как все заканчивается за 1 нс… В любом случае, я думаю, что эти результаты, вероятно, настолько портативны, насколько вообще могут быть микротесты.)
Итак, получается, что оператор import
более чем в два раза быстрее; после этого вызов функции немного медленнее, но этого недостаточно, чтобы компенсировать более дешевый import
. (Имейте в виду, что ваш тест выполнял import
для каждого вызова. В реальном коде, конечно, вы склонны вызывать вещи намного чаще, чем один раз за import
. Итак, мы действительно рассматриваем пограничный случай, который будет редко влияет на реальный код, но пока вы помните об этом, мы продолжаем.)
Концептуально вы можете понять, почему оператор from … import
занимает больше времени: у него больше работы. Первая версия должна найти модуль, при необходимости скомпилировать его и выполнить. Вторая версия должна сделать все это, а затем также извлечь sqrt
и вставить его в глобальные переменные вашего текущего модуля. Таким образом, он должен быть хотя бы немного медленнее.
Если вы посмотрите на байт-код (например, используя модуль dis
и вызвав dis.dis('import math')
), именно в этом разница. Сравнивать:
0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (None)
6 IMPORT_NAME 0 (math)
9 STORE_NAME 0 (math)
12 LOAD_CONST 1 (None)
15 RETURN_VALUE
… to:
0 LOAD_CONST 0 (0)
3 LOAD_CONST 1 (('sqrt',))
6 IMPORT_NAME 0 (math)
9 IMPORT_FROM 1 (sqrt)
12 STORE_NAME 1 (sqrt)
15 POP_TOP
16 LOAD_CONST 2 (None)
19 RETURN_VALUE
Дополнительные манипуляции со стеком (LOAD_CONST
и POP_TOP
), вероятно, не имеют большого значения, и использование другого аргумента для STORE_NAME
вряд ли вообще имеет значение… но IMPORT_FROM
— это значительный дополнительный шаг.
Удивительно, но быстрая и грязная попытка профилировать код IMPORT_FROM
показывает, что большая часть затрат на самом деле связана с поиском подходящих глобальных переменных для импорта. Я не знаю, почему, но… это означает, что импорт целого ряда имен должен быть ненамного медленнее, чем импорт одного. И, как вы указали в комментарии, это именно то, что вы видите. (Но не придавайте этому слишком большого значения. Есть много причин, по которым IMPORT_FROM
может иметь большой постоянный коэффициент и только небольшой линейный, и мы не собираемся набрасывать на него огромное количество имен.)
И последнее: если это действительно имеет значение в реальном коде, и вы хотите получить лучшее из обоих миров, import math; sqrt = math.sqrt
быстрее, чем from math import sqrt
, но дает такое же небольшое ускорение времени поиска/вызова. (Но опять же, я не могу представить ни одного реального кода, в котором это имело бы значение. Единственный раз, когда вас будет волновать, сколько времени займет sqrt
, это когда вы вызываете его миллиард раз, и в этот момент вам уже все равно, сколько времени импорт занимает. Плюс, если вам действительно нужно оптимизировать это, создайте локальную область и привяжите sqrt
там, чтобы полностью избежать глобального поиска.)
person
abarnert
schedule
09.08.2013
python -mtimeit "from math import pow, sqrt, sin, fabs, ceil, floor, fmod"
занимает только часть больше, чемpython -mtimeit "from math import pow"
, что не поддерживает теорию. - person NPE   schedule 09.08.2013