Оптимизация импорта модулей в Python - PullRequest
4 голосов
/ 08 мая 2011

Я читаю Справочник Дэвида Бизли по Python, и он подчеркивает:

Например, если вы выполняли много операций с квадратным корнем, это быстрее использовать из математического импорта sqrt'и' sqrt (x) 'вместо того, чтобы вводить' math.sqrt (x) '.

и:

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

Я решил попробовать:

first ()

def first():
    from collections import defaultdict
    x = defaultdict(list)

second ()

def second():
    import collections
    x = collections.defaultdict(list)

Результаты были:

2.15461492538
1.39850616455

Подобные оптимизации, вероятно, не имеют для меня значения.Но мне любопытно, почему противоположность написанного Бизли оказывается правдой.И обратите внимание, что есть разница в 1 секунду, что особенно важно, если задание тривиально.

Почему это происходит?

ОБНОВЛЕНИЕ:

Я получаю время как:

print timeit('first()', 'from __main__ import first');
print timeit('second()', 'from __main__ import second');

Ответы [ 6 ]

6 голосов
/ 08 мая 2011

from collections import defaultdict и import collections должны быть вне повторяющихся циклов синхронизации, так как вы не будете повторять их выполнение.

Я предполагаю, что синтаксис from должен выполнять больше работы, чем import синтаксис.

Используя этот тестовый код:

#!/usr/bin/env python

import timeit

from collections import defaultdict
import collections

def first():
    from collections import defaultdict
    x = defaultdict(list)

def firstwithout():
    x = defaultdict(list)

def second():
    import collections
    x = collections.defaultdict(list)

def secondwithout():
    x = collections.defaultdict(list)

print "first with import",timeit.timeit('first()', 'from __main__ import first');
print "second with import",timeit.timeit('second()', 'from __main__ import second');

print "first without import",timeit.timeit('firstwithout()', 'from __main__ import firstwithout');
print "second without import",timeit.timeit('secondwithout()', 'from __main__ import secondwithout');

Я получаю результаты:

first with import 1.61359190941
second with import 1.02904295921
first without import 0.344709157944
second without import 0.449721097946

Который показывает, сколько стоит повторный импорт.

4 голосов
/ 08 мая 2011

Я также получу аналогичные соотношения между first(.) и second(.), единственное отличие состоит в том, что время в микросекундах.

Я не думаю, что ваше время измеряет что-то полезное. Попробуйте выяснить лучшие тестовые случаи!

Обновление:
FWIW, вот несколько тестов, чтобы поддержать точку зрения Дэвида Бизли.

import math
from math import sqrt

def first(n= 1000):
    for k in xrange(n):
        x= math.sqrt(9)

def second(n= 1000):
    for k in xrange(n):
        x= sqrt(9)

In []: %timeit first()
1000 loops, best of 3: 266 us per loop
In [: %timeit second()
1000 loops, best of 3: 221 us per loop
In []: 266./ 221
Out[]: 1.2036199095022624

Так что first() примерно на 20% медленнее, чем second().

1 голос
/ 09 мая 2011

Существует также вопрос эффективности чтения / понимания исходного кода. Вот реальный живой пример (код из вопроса stackoverflow )

Оригинал:

import math

def midpoint(p1, p2):
   lat1, lat2 = math.radians(p1[0]), math.radians(p2[0])
   lon1, lon2 = math.radians(p1[1]), math.radians(p2[1])
   dlon = lon2 - lon1
   dx = math.cos(lat2) * math.cos(dlon)
   dy = math.cos(lat2) * math.sin(dlon)
   lat3 = math.atan2(math.sin(lat1) + math.sin(lat2), math.sqrt((math.cos(lat1) + dx) * (math.cos(lat1) + dx) + dy * dy))
   lon3 = lon1 + math.atan2(dy, math.cos(lat1) + dx)
   return(math.degrees(lat3), math.degrees(lon3))

Альтернатива:

from math import radians, degrees, sin, cos, atan2, sqrt

def midpoint(p1, p2):
   lat1, lat2 = radians(p1[0]), radians(p2[0])
   lon1, lon2 = radians(p1[1]), radians(p2[1])
   dlon = lon2 - lon1
   dx = cos(lat2) * cos(dlon)
   dy = cos(lat2) * sin(dlon)
   lat3 = atan2(sin(lat1) + sin(lat2), sqrt((cos(lat1) + dx) * (cos(lat1) + dx) + dy * dy))
   lon3 = lon1 + atan2(dy, cos(lat1) + dx)
   return(degrees(lat3), degrees(lon3))
1 голос
/ 08 мая 2011

Полагаю, ваш тест смещен, и вторая реализация выигрывает от первой, уже загрузившей модуль, или только от недавней загрузки.

Сколько раз вы пробовали это? Вы переключили заказ и т.д ..

1 голос
/ 08 мая 2011

first() ничего не сохраняет, так как к модулю необходимо получить доступ для импорта имени.

Кроме того, вы не предоставляете свою методологию синхронизации, но, учитывая имена функций, кажется, что first() выполняет первоначальный импорт, который всегда длиннее, чем последующие, поскольку модуль должен быть скомпилирован и выполнен.

0 голосов
/ 09 мая 2011

Напишите свой код как обычно, импортируя модуль и ссылаясь на его модули и константы как module.attribute. Затем добавьте префикс ваших функций к декоратору для связывания констант или свяжите все модули в вашей программе, используя функцию bind_all_modules ниже:

def bind_all_modules():
    from sys import modules
    from types import ModuleType
    for name, module in modules.iteritems():
        if isinstance(module, ModuleType):
            bind_all(module)

def bind_all(mc, builtin_only=False, stoplist=[],  verbose=False):
    """Recursively apply constant binding to functions in a module or class.

    Use as the last line of the module (after everything is defined, but
    before test code).  In modules that need modifiable globals, set
    builtin_only to True.

    """
    try:
        d = vars(mc)
    except TypeError:
        return
    for k, v in d.items():
        if type(v) is FunctionType:
            newv = _make_constants(v, builtin_only, stoplist,  verbose)
            try: setattr(mc, k, newv)
            except AttributeError: pass
        elif type(v) in (type, ClassType):
            bind_all(v, builtin_only, stoplist, verbose)
...