Возможно вы искали: 'Пираты Карибского моря'

May 15 2025 19:40:11
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96111483
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18357
• Обзор The Walking ... 18801
• Обзор DMC: Devil M... 19879
• Обзор на игру Valk... 15877
• Обзор на игру Stars! 17764
• Обзор на Far Cry 3 17948
• Обзор на Resident ... 16024
• Обзор на Chivalry:... 17508
• Обзор на игру Kerb... 17981
• Обзор игры 007: Fr... 16619
Превью о играх
• Превью к игре Comp... 17960
• Превью о игре Mage... 14464
• Превью Incredible ... 14721
• Превью Firefall 13479
• Превью Dead Space 3 16334
• Превью о игре SimC... 14730
• Превью к игре Fuse 15442
• Превью Red Orche... 15542
• Превью Gothic 3 16343
• Превью Black & W... 17354
Главная » Статьи » Разное » Советы по увеличению производительности Python скриптов (python optimization tune speed)

Советы по увеличению производительности Python скриптов (python optimization tune speed)

Ключевые слова: python, optimization, tune, speed, (найти похожие документы)

From: OmskLUG
Date: Sun, 5 Nov 2007 17:02:14 +0000 (UTC)
Subject: Советы по увеличению производительности Python скриптов

Оригинал: http://omsk.lug.ru/Python/Perfomance/PerfomanceTips/print
http://omsk.lug.ru/Python/Perfomance/GentooHacks
http://wiki.python.org/moin/PythonSpeed/PerformanceTips

Заметки о производительности

Эта статья описывает различные "хитрости", которые позволяют
увеличить скорость Вашей питон-программы. Если информация поступает
от кого-то еще, я стараюсь указывать источник.

Питон несколько раз сильно менялся с тех пор, как я ( Skip
Montanaro -- прим. j2a) написал свою страничку "быстрый питон"
примерно в 1996, так что с тех времен кое-что изменилось. Я перешел
на вики и надеюсь, это поможет поддерживать страничку в актуальном
состоянии.

Замечание: Вы всегда должны проверять описанные рецепты в своем
приложении и в используемой версии Питон, а не слепо доверять,
что "это быстрее чем вон то".

Также с тех пор появились некоторые новые оригинальные пакеты, такие
как: Pyrex, Psyco, Weave и [26] PyInline, которые могут
очень прилично увеличить скорость Вашего приложения, позволяя
критичный к скорости код реализовывать на C.


Оптимизируй то, что нужно

Вы можете узнать, что программа медленна, только если она правильная.
Только после того, как программа выдала корректный результат, можно
увидеть, что правильная программа -- медленна.

Когда обнаружено, что программа медленна, профилировка поможет
определить части программы, где теряется больше всего времени. Полный
но быстрый (для запуска) юнит-тест впоследствии поможет
проконтролировать, что оптимизация не "повредила" корректности
программы. Кратко:
1. Сделай правильно
2. Проверь, правильно ли
3. Профилируй
4. Оптимизируй
5. Повтори с шага 2

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


Выбирайте правильные структуры данных

To bee done... (пока данный раздел не описан в оригинале -- прим. j2a)


Сортировка

Сортировка списков базовых объектов Питона в целом весьма эффективна.
Метод сортировки списков в качестве опционального аргумента берет
функцию сравнения, так что порядок и поведение сортировки могут быть
изменены. Это достаточно удобно, хотя может сильно замедлить Вашу
сортировку, поскольку функция сравнения будет вызываться много раз.

В Питоне версии 2.4, Вы можете передавать встроенной функции
сортировки аргумент "key", и похоже, по производительности это будет
наилучший вариант.

Если у вас старые версии Питона (ниже 2.4), используйте следующий
совет Гвидо ван Россума:
альтернативный способ увеличить скорость сортировки -- создать список
кортежей, чей первый элемент ключ сортировки такой, что нужным образом
сортируется стандартной сортировкой, а второй элемент -- элемент
оригинального списка. Это также называется преобразованием Шварца,
или [28] decorate-sort-undecorate (DSU).

Например, полагаем, что у Вас есть список кортежей, который Вы хотите
отсортировать по n-ому полю каждого кортежа. Следующая функция делает
это:

def sortby(somelist, n):
nlist = [(x[n], x) for x in somelist]
nlist.sort()
return [val for (key, val) in nlist]

(списковые выражения появились начиная с версии 2.0 питона -- прим.
j2a)


Точно такое же поведение для текущего списка (сортировка "на месте")
также достижимо следующим образом:

def sortby_inplace(somelist, n):
somelist[:] = [(x[n], x) for x in somelist]
somelist.sort()
somelist[:] = [val for (key, val) in somelist]
return


А ниже пример использования:

>>> somelist = [(1, 2, 'def'), (2, -4, 'ghi'), (3, 6, 'abc')]
>>> somelist.sort()
>>> somelist
[(1, 2, 'def'), (2, -4, 'ghi'), (3, 6, 'abc')]
>>> nlist = sortby(somelist, 2)
>>> sortby_inplace(somelist, 2)
>>> nlist == somelist
True
>>> nlist = sortby(somelist, 1)
>>> sortby_inplace(somelist, 1)
>>> nlist == somelist
True


Совет от Tim Delaney:
Начиная с Питон 2.3 сортировка стабильная (точнее, она стабильна
в CPython 2.3 и наверняка будет стабильна в 2.4).
В Питоне 2.4 добавлен дополнительный параметр "key", который легко
использовать:

import operator
sort(nlist, key=operator.itemgetter(n))


Учтите, что исходный элемент никогда не используется в сортировке,
только возвращаемый ключ, это эквивалентно:

nlist = [(x[i], i, x) for (i, x) in enumerate(nlist)]
nlist.sort()
nlist = [val for (key, index, val) in nlist]


Конкатенация строк

Внимание: данный совет следует уточнить для Python 2.5, в нем
конкатенация строк (не unicode) быстрая.

Строки в Питоне неизменяемы. Этот факт часто упускают из виду
начинающие Питон-программисты. Неизменяемость имеет как преимущества,
так и недостатки. Плюсы: строки могут использоваться в качестве ключей
в словарях и отдельные копии могут быть разделены между различными
переменных (Питон автоматически разделяет одно- или двух-символьные
строки). Минусы: Вы не можете сделать что-нибудь вроде "поменять
все 'а' на 'б'" в текущей строке. Вместо этого Вы должны создать новую
строку с желаемыми свойствами. Последовательные копирования могут
привести к приличной потере производительности в Питон-программ --
см. [29] ConcatenationTestCode.

Старайтесь избегать такого кода:

s = ""
for substring in list:
s += substring


Вместо этого используйте s = "".join(list).
Другая очень распространенная и катастрофичная (по производительности)
ошибка совершается при построении длинных строк. Так что. если

Вы генерируете строки последовательно, вместо

s = ""
for x in list:
s += some_function(x)


используйте

slist = [some_function(elt) for elt in somelist]
s = "".join(slist)


Избегайте

out = "<html>" + head + prologue + query + tail + "</html>"


Вместо этого, лучше пишите так:

out = "<html>%s%s%s%s</html>" % (head, prologue, query, tail)


Более того, для читабельности (не имеет ничего общего
с эффективностью, разве что Вашей как программиста), используйте
словарь подстановки:

out = "<html>%(head)s%(prologue)s%(query)s%(tail)s</html>" % locals()


Последние два варианта намного быстрее, особенно ощутимо когда
суммируется по многочисленным вызовам CGI-скриптов, к тому же их
гораздо проще изменять.


Циклы

Питон поддерживает несколько конструкций циклов. Цикл for наиболее
часто используемый. Он "пробегает" по всем элементам
последовательности, присваивая каждый из них переменной цикла. Если
тело цикла простое, то накладные расходы (оверхед) интерпретатора
на сам цикл for могут составлять существенную часть всех накладных
расходов. Это то место, где функция map весьма полезна. Вы можете
считать, что map это for, переписанный на C. Единственное ограничение,
что "тело цикла" map должно быть вызовом функции. Списковые
выражения, помимо более понятного синтаксиса, также быстры или еще
быстрей, чем map.

Ниже приведен пример. Вместо цикла по списку слов и преобразования
их в верхний регистр:

newlist = []
for word in oldlist:
newlist.append(word.upper())


Вы можете использовать map для того чтобы "перенести" цикл
из интерпретатора в скомпилированный C-код:

newlist = map(str.upper, oldlist)


Списковые выражения были добавлены в Питон 2.0. Они дают синтаксически
более компактный и эффективный способ написания подобного цикла:

newlist = [s.upper() for s in oldlist]


Выражения-генераторы были добавлены в версии 2.4 Питона. Это функции,
более-или-менее как списковые выражения или как map, но без накладных
расходо на генерацию полного списка сразу. Вместо этого,
они возвращают объект генератора, по которому может проходить итерация
пошагово:

newlist = (s.upper() for s in oldlist)


Какой метод применять зависит от используемой версии Питона
и характеристик исходных данных.

Гвидо ван Россум написал более подробное(и более лаконичное)
объяснение оптимизации циклов в [31] своем эссе, крайне рекомендуется
к прочтению. (Существует два перевода этого эссе: ~1, ~2 --
прим. j2a )


Избегая точек...

Полагаете, что Вы не можете использовать map или списковые выражения?
Возможны, вы слишком "привыкли" к циклу for. Пример цикла for выше
имеет еще одно неэффективное место. Как newlist.append
так и word.upper являются ссылками на функции и вычисляются заново
при каждом вызове (т.е. при каждой итерации цикла). Оригинальный цикл
можно переписать так:

upper = str.upper
newlist = []
append = newlist.append
for word in list:
append(upper(word))


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


Локальные переменные

И последнее, чем можно увеличить скорость не-map версии цикла for --
это использовать локальные переменные там где только можно. Если
представить вышеуказанный цикл функцией, то append и upper становятся
локальными переменными. Доступ Питона к локальным переменным гораздо
более эффективен, чем доступ к глобальным переменным.

def func():
upper = str.upper
newlist = []
append = newlist.append
for word in words:
append(upper(word))
return newlist


В то время, когда я ( Skip Montanaro -- прим. j2a) писал
эти строки, я использовал 100МГЦ Pentium и BSDi. И я получил следующие
времена на преобразование списка слов из /usr/share/dict/words (38,470
на текущий момент):

Версия Время, с
Оригинальный цикл 3.47
Убрав точки 2.45
Локальные переменные+без точек 1.79
Используя функцию map 0.54


Инициализация элементов словаря

Допустим, что Вы составляете словарь частоты встречаемых слов и Вы
уже разбили Ваш текст на слова. Наверное, Вы захотите сделать что-то
подобное:

wdict = {}
for word in words:
if word not in wdict:
wdict[word] = 0
wdict[word] += 1


Кроме самого первого раза, при каждой итерации word, выражение if
ложно. Если Вы подсчитываете большое количество слов, есть
вероятность, что большинство слов встречаются более одного раза.

В ситуации, когда инициализация значения происходит один раз и велика
вероятность, что значение будет встречаться много раз, выгоднее
использовать try:

wdict = {}
for word in words:
try:
wdict[word] += 1
except KeyError:
wdict[word] = 1


Важно отметить, что перехватывать нужно только ожидаемое KeyError
исключение, а не оставлять просто except, чтобы не "словить"
исключение, которые не обрабатывается внутри try/except.

Третий вариант стал возможен в версии Питона 2.0 и старше. Словари
теперь имеют метод get(), который возвращает значение по умолчанию
если искомый ключ не найден в словаре. Это упрощает цикл:

wdict = {}
get = wdict.get
for word in words:
wdict[word] = get(word, 0) + 1


Когда я ( Skip Montanaro -- прим. j2a) писал этот раздел, были
явные ситуации, когда один из первых двух вариантов был быстрее.
Похоже, что сейчас все три подхода дают примерно одинаковую
производительность (примерно с разницей 10% относительно друг друга),
более или менее зависимо от свойств списка слов.

Если данные, хранимые в слваре -- объекты или (изменяемые) списки,
то Вы также можете использовать метод dict.setdefault, т.е.:

...
wdict.setdefault(key, []).append(new_element)


Такой подход избавляет от необходимости дважды искать значение
в словаре по ключу.


Накладные расходы на import

import может быть выполнен в любом месте. Часто это используется
внутри функции чтобы скрыть из глобального пространства имен и/или
сократить время начального запуска программы. Хотя интерпретатор
Питона оптимизирован на то, чтобы не импортировать один и тот
же модуль несколько раз, в некоторых ситуациях последовательные
импорты могут сильно повлиять на производительность.

Рассматриваем следующие фрагменты кода (изначально от Greg McFaralne,
мне так кажется, -- я нашел этот код "безымянным" в [37]
comp.lang.python и позже приписал Greg'у авторство, получив данные
из другого источника)

def doit1():
import string ###### импорт внутри функции
string.lower('Python')

for num in range(100000):
doit1()


или:

import string ###### импорт вне функции
def doit2():
string.lower('Python')

for num in range(100000):
doit2()


doit2 выполняется гораздо быстрее чем doit1, даже если ссылка
на модуль string глобальна в doit2. Ниже приведена сессия
интерпретатора Питон (используя Питон 2.3 и новый модуль timeit,
который показывает насколько второй быстрей чем первый):

>>> def doit1():
... import string
... string.lower('Python')
...
>>> import string
>>> def doit2():
... string.lower('Python')
...
>>> import timeit
>>> t = timeit.Timer(setup='from __main__ import doit1', stmt='doit1()')
>>> t.timeit()
11.479144930839539
>>> t = timeit.Timer(setup='from __main__ import doit2', stmt='doit2()')
>>> t.timeit()
4.6661689281463623


Методы строк, введенны в язык в Питоне 2.0. Они дают возможность
вообще избежать импорта и выполняются намного быстрее:

def doit3():
'Python'.lower()

for num in range(100000):
doit3()


Ниже -- доказательство от timeit:

>>> def doit3():
... 'Python'.lower()
...
>>> t = timeit.Timer(setup='from __main__ import doit3', stmt='doit3()')
>>> t.timeit()
2.5606080293655396


Вышеприведенные примеры немного натянуты, но общий принцип понятен.

Замечание: переместив импорт внутрь функции, можно увеличить начальную
скорость загрузки модуля, особенно если импортируемой модуль особо
не нужен. Это так называемая "отложенная" оптимизация -- избегать
работу (импортирование модуля, которое может быть затратным) до тех
пор, пока Вы точно не будете уверены, что Вам это необходимо.

Значительное преимущество будет только в том случае, если данный
модуль более нигде не будет импортироваться вообще (из любого другого
модуля) -- если модуль уже загружен (часто в случае стандартных
модулей, таких как string или re), то никакого выигрыша не будет.

Для того чтобы посмотреть, какие модули загружены --
см. в sys.modules.

Хороший пример отложенного импорта:

email = None

def parse_email():
global email
if email is None:
import email
...


В данном случае, модуль email будет импортирован один раз, при первом
запуске parse_email().


Агрегация данных

Накладные расходы на вызов функции в Питоне достаточно велики,
особенно по сравнению со встроенными функциями. Поэтому крайне
рекомендуется чтобы там, где это возможно, функции обрабатывали
агрегаторы данных. Ниже приведен пример (немного искуcственный):

import time
x = 0
def doit1(i):
global x
x = x + i

list = range(100000)
t = time.time()
for i in list:
doit1(i)

print "%.3f" % (time.time()-t)


и

import time
x = 0
def doit2(list):
global x
for i in list:
x = x + i

list = range(100000)
t = time.time()
doit2(list)
print "%.3f" % (time.time()-t)


Ниже -- пример интерактивной сессии:

>>> t = time.time()
>>> for i in list:
... doit1(i)
...
>>> print "%.3f" % (time.time()-t)
0.758
>>> t = time.time()
>>> doit2(list)
>>> print "%.3f" % (time.time()-t)
0.204


Даже написанный на Питоне, второй пример примерно в четыре раза
быстрее чем первый. Будь doit написан на C, разница была бы еще более
заметна (замена Питон-цикла for на C-цикл, плюс удаление большого
количества вызовов функций).


Делать проверки асинхронных событий реже

Питон-интерпретатор выполняет некоторые периодические проверки.
В частности, он решает, запускать или не запускать очередной поток
(thread); запускать ожидающий (pending) вызов или не запускать (обычно
это вызов обработчика сигналов). В большинстве случаев, такие проверки
ничего не делают, так что их периодическое выполнение может понизить
производительность. Эти функции находятся в модуле sys;
setcheckinterval указывает как часто интерпретатор должен выполнять
эти периодические проверки. Для Питона версий до 2.3 по умолчанию
это 10. В 2.3 его повысили до 100. Если Вы не работаете с потоками
и Вы не думаете, что будете обрабатывать много сигналов, увеличение
значения этой переменной может увеличить производительность, иногда
существенно.


Python - не C

Он также не Perl, не Java, не C++ и не Haskell. Будьте осторожны
в проецировании своих знаний с других языков на Питон. Простой пример
демонстрирует это:

% timeit.py -s 'x = 47' 'x * 2'
1000000 loops, best of 3: 0.574 usec per loop
% timeit.py -s 'x = 47' 'x << 1'
1000000 loops, best of 3: 0.524 usec per loop
% timeit.py -s 'x = 47' 'x + x'
1000000 loops, best of 3: 0.382 usec per loop


Теперь аналогичный код на C (показана версия только со сложением):

#include <stdio.h>

int
main (int argc, char **argv) {
int i = 47;
int loop;
for (loop=0; loop<500000000; loop++)
i + i;
}


и время выполнения:

% for prog in mult add shift ; do
< for i in 1 2 3 ; do
< echo -n "$prog: "
< /usr/bin/time ./$prog
< done
< echo
< done

mult: 6.12 real 5.64 user 0.01 sys
mult: 6.08 real 5.50 user 0.04 sys
mult: 6.10 real 5.45 user 0.03 sys
add: 6.07 real 5.54 user 0.00 sys
add: 6.08 real 5.60 user 0.00 sys
add: 6.07 real 5.58 user 0.01 sys
shift: 6.09 real 5.55 user 0.01 sys
shift: 6.10 real 5.62 user 0.01 sys
shift: 6.06 real 5.50 user 0.01 sys


Заметьте, что в Питоне существенным преимуществом обладает сложение
числа с самим собой, перед умножением на два или перед сдвигом на один
бит влево. На C и на всех современных архитектурах компьютеров, каждая
из этих трех арифметических операций кодируется в одну машинную
инструкцию, так что нет разницы (для C -- прим. j2a), какую
Вы выберите.

Частый "тест", который проделывают новички в Питоне, проецируя
Perl-идиому:

while (<>) {
print;
}


в Питон код, так что это выглядит примерно как:

import fileinput

for line in fileinput.input():
print line,


и приходят к заключению, что Питон гораздо медленнее Perl. Как уже
много раз говорилось, Питон медленнее Perl на одних задачах и быстрее
на других. Относительная производительность часто зависит от опыта
в каждом из двух языков.


Используйте xrange вместо range

В Питоне есть два способа получить диапазон чисел: range и xrange.
Большинство людей знают range, потомучто у него очевидное имя. xrange
в конце алфавита и поэтому его знают в горздо меньшей степени.

xrange -- генератор, почти идентичный следующему Питон-2.3 коду:


def xrange(start, stop=None, step=1):
if stop is None:
stop = start
start = 0
else:
stop = int(stop)
start = int(start)
step = int(step)

while start < stop:
yield start
start += step

За исключением того, что он реализован на чистом C.

xrange имеет кое-какие ограничения. В частности, он работает только
с int, он не работает с long, float (они преобразовываются в int).

Между тем, он более бережно использует память, и если Вы отдельно
не храните yielded-объекты, то существует только один yielded-объект.
Разница вот в чем: когда Вы вызываете range, он создает список (list),
содержащий большое количество чисел (int, long или float). Весь список
создается сразу и все его элементы хранятся всё время. Это может быть
весьма расточительно, особенно когда количество чисел велико.

С другой стороны -- xrange -- не создает элементы сразу, он только
создает объект диапазона. Числа создаются тогда, когда Вы запрашиваете
данные у генератора, т.е. во время цикла по нему. Для примера:

xrange(sys.maxint) # Нет цикла, нет вызова .next, так что числа не выводятся


И по этой причине код сразу выполняется. Если Вы подставите range,
то Питон "зависнет" -- слишком времени займет генерация и размещение
в памяти sys.maxint чисел (на обычном PC это примерно 2.1 миллиарда).
В конце-концов он исчерпает всю память и выйдет.

В версиях Питона до 2.2, xrange объекты также поддерживали
оптимизации, такие как быстрое тестирование членства (i in xrange(n)).
Эта особенность была удалена из 2.2 ввиду неиспользования.


Переназначение функций на лету

Скажем, у Вас есть функция

class Test:
def check(self,a,b,c):
if(a):
self.str = b*100
else:
self.str = c*100

a = Test()
def example():
for i in xrange(0,100000):
a.check(i,"b","c")

import profile
profile.run("example()")


И Вы полагаете, что эта функция вызывается во многих местах по многу
раз. И если Вы видите, что выражение if замедляет скорость всегда
кроме первого раза, Вы можете сделать так:

class Test2:
def check(self,a,b,c):
self.str = b*100
self.check = self.check_post
def check_post(self,a,b,c):
self.str = c*100

a = Test2()
def example2():
for i in xrange(0,100000):
a.check(i,"b","c")

import profile
profile.run("example2()")


Итак, этот пример весьма натянут, однако, если выражение if весьма
сложно (или в нем много точек), Вы можете выиграть ситуацию, если
выполните его [выражение], будучи уверенным, что это выражение будет
истинно только в начале.


Профилирование кода

Первый шаг к увеличению скорости Вашей программы -- выяснение узких
мест. Не имеет смысла оптимизировать код, который вообще
не выполняется или выполняется и так быстро. Я ( Skip Montanaro --
прим. j2a) использую два модуля, которые помогают мне выделить
хотспоты в моем коде, profile и trace. В дальнейшем, я стал
использовать и timeit, который появился в Питоне версии 2.3


Модуль profile

Модуль profile включен в стандартную поставку Питона.
Его достаточно легко использовать для профилировки выполнения набора
функций. Полагая, что Ваша главная функция main вызывается
без аргументов и Вы хотите запустить ее под контролем модуля profile.
В самом простом случае достаточно:

import profile
profile.run('main()')


Когда main() завершит выполнение, модуль profile распечатает таблицу
вызовов функций и времена выполнения. Вывод может быть настроен
при помощи класса Stats также включенного в состав этого модуля.
В Питоне версии 2.4 profile позволяет профилировать встроенные функции
Питона и функции из модулей.


Модуль hotshot

Пакет hotshot, появившийся в Питоне 2.2, позиционируется
как замена модулю profile. Основной модуль написан на C, так что
использование hotshot должно меньше влиять на производительность и,
поэтому давать более аккуратные результаты по Вашей программе. Также
в поставку Питона (в папке Tools/scripts) входит hotshotmain.py,
который позволяет с командной строки запускать Вашу программу
под контролем hotshot.


Модуль trace

Модуль trace -- это развитие profile, которое я [42] Skip
Montanaro -- прим. j2a) написал изначально для выполнения некоторых
черновых тестов. С тех пор, как я написал его, он был серъезно
доработан другими людьми. Так что начиная с Питон 2.0 Вы можете найти
trace.py в папке Tools/scripts дистрибутива Питона. Начиная с Питона
весрии 2.3, он включен в стандартную библиотеку (папка Lib). Вы можете
скопировать его в папку с локальным bin, поставить право выполнения
и потом непосредственно запустить. Он позволяет выполнять трассировку
скриптов из командной строки:

% trace.py -t spam.py eggs


В Питоне 2.4 использовать его еще проще. Просто запустите python -m
trace.

Отдельной документации по нему нет, однако Вы можете выполнить pydoc
trace для просмотра встроенной документации.


Перевод http://gentooexperimental.org/~ferringb/bzr/pkgcore/HACKING

Не гарантируется, что приведенный код корректен ;)
Вся статистика для sempron 1.6ghz и python 2.4.2 (для платформы linux,
я так понимаю -- прим. j2a)


len

Будь внимателен насчет того, что в действительности делает
интерпретатор

Не использууй len(list_instance) когда тебе лишь нужно узнать, пустой
или не пустой список

l=[1]
if l: blah
# вместо
if len(l): blah


python смотрит вначале __nonzero__, потом __len__. Намного быстрее
будет, если ты явно укажешь, что смотреть.

$ python -m timeit -s 'l=[]' 'if len(l) > 0: pass'
1000000 loops, best of 3: 0.705 usec per loop
$ python -m timeit -s 'l=[]' 'if len(l): pass'
1000000 loops, best of 3: 0.689 usec per loop
$ python -m timeit -s 'l=[]' 'if l: pass'
1000000 loops, best of 3: 0.302 usec per loop


has_key

Не используй has_key, лучше положись на оператор 'in'.

$ python -m 'timeit' -s 'd=dict(zip(range(1000), range(1000)))' 'd.has_key(1999999)'
1000000 loops, best of 3: 0.512 usec per loop
$ python -m 'timeit' -s 'd=dict(zip(range(1000), range(1000)))' '1999999 in d'
1000000 loops, best of 3: 0.279 usec per loop


Python интерпретирует 'in' используя __contains__ объекта. Делает
он это быстрей, выполняя getattr, чем текущий python-код. Например,
код приведенный выше, использует d.__contains__; если ты сделаешь
d.has_key или d.__contains__, то по скорости это будет одинаково.
Используя 'in', интерпретатор делает поиск (lookup), который быстрее.
Так что... будь внимателен, как интерпретатор выполняет твой код.
Доступ к атрибутам из python-кода медленнее, чем <<изнутри>>
интерпретатора.

Если сомневаешься -- python -m timeit спасет тебя ;)


[], {} как значения по умолчанию

Не используй [] или {} как значения по умолчанию в определениях
функций/процедур.

>>> def f(default=[]):
>>> default.append(1)
>>> return default
>>> print f()
[1]
>>> print f()
[1,1]


Значения по умолчанию инициируются, когда определяются
функции/классы/методы, не в момент вызова.
Конечный результат, если использует изменяемое значение по умолчанию,
должен использовать None и проверять это;
это можно не делать если ты точно знаешь, что это значение
не меняется.


Карри-функции

Видимые карри-функции (см. напр. И опять о функциональном
программировании на Python -- прим. j2a) должны быть документированы.
Когда ты используешь методы карринга (pkgcore.util.currying))
для изменений функций, документируй их при помощи pretty_docs.
Если этого не делать, то вывод pydoc для такого объекта не будет особо
полезным.


Юнит-тесты

Весь код должен иметь тесты. Мы используем twisted.trial; по идее
работает на python >= 2.2 (на python <2.2 часты ложные срабатывания
в тестах spawn). Регрессия должны обязательно иметь тесты,
исключая таким образом дурацкие ошибки (опечатки, например).
Еще допустимо, когда на готовый код не хватает тестов, но при
объединении/интегрировании тесты обязательны.

Единственное, что (на данный момент) не требует тестов --
это интерактивность ebuild; тестирование интерфейса достаточно сложно,
хотя по идее, должно быть реализовано.
Если тесты отсутствуют (например, автор не написал их изначально),
крайне желательно создание новых тестов :)


Код, работающий с ФС

Если дело касается кода, работающего с ФС, то обычно лучше сделать
try, чем сначала спросить, а потом сделать try... но ты должен
проверить это ;)

...существующий файл (но пустой, чтобы исключить влияние потерь
при чтении)...

$ echo > dar
$ python -m 'timeit' -s 'import os' 'os.path.exists(<<dar>>) and open(<<dar>>).read()'
10000 loops, best of 3: 36.4 usec per loop
$ python -m 'timeit' -s 'import os' $'trySurprisedpen(<<dar>>).read()nexcept IOError: pass'
10000 loops, best of 3: 22 usec per loop
...несуществующий файл...
$ rm foo
$ python -m 'timeit' -s 'import os' 'os.path.exists(<<foo>>) and open(<<foo>>).read()'
10000 loops, best of 3: 29.8 usec per loop
$ python -m 'timeit' -s 'import os' $'trySurprisedpen(<<foo>>).read()nexcept IOError: pass'
10000 loops, best of 3: 27.7 usec per loop


Есть разница, не правда ли?

Заметь, что это касается только кода, работающего с ФС; системные
вызовы не быстры -- если это не переключаемые системные вызовы, смотри
следющий раздел.


Исключения

Отлов исключений в python-коде (в отличии от CPython) -- медленны.
статистика для python-2.4.2:

Возбуждение исключения:

$ python -m 'timeit' -s 'd=dict(zip(range(1000), range(1000)))' $'try: d[1999]nexcept KeyError: pass'
100000 loops, best of 3: 8.7 usec per loop
$ python -m 'timeit' -s 'd=dict(zip(range(1000), range(1000)))' $'1999 in d and d[1999]'
1000000 loops, best of 3: 0.492 usec per loop


В случае, если исключение не возбуждается, потеря ресурсов
на try/except:

$ python -m 'timeit' -s 'd=dict(zip(range(1000), range(1000)))' $'try: d[0]nexcept KeyError: pass'
1000000 loops, best of 3: 0.532 usec per loop
$ python -m 'timeit' -s 'd=dict(zip(range(1000), range(1000)))' $'d[0]'
1000000 loops, best of 3: 0.407 usec per loop


Не советую писать код, которые не защищает сам себя, но лишь будь
внимателен насчет того, что делает твой код; и обращай внимание,
что исключения в python-коде весьма накладны, в силу вызываемой
ими "машинерии".


setdefault для словаря

Еще один пример насчет использования или неиспользования setdefault
и get методов словаря:

Ключ существует:
Через возбуждение исключения:

$ python -m timeit -s 'd=dict.fromkeys(range(100))' 'try: x=d' 'except KeyError: x=42'
1000000 loops, best of 3: 0.548 usec per loop


d.get:

$ python -m timeit -s 'd=dict.fromkeys(range(100))' 'x=d.get(1, 42)'
1000000 loops, best of 3: 1.01 usec per loop


Ключ не существует:
Через исключение:

$ python -m timeit -s 'd=dict.fromkeys(range(100))' 'try: x=d[101]' 'except KeyError: x=42'
100000 loops, best of 3: 8.8 usec per loop


d.get:

$ python -m timeit -s 'd=dict.fromkeys(range(100))' 'x=d.get(101, 42)'
1000000 loops, best of 3: 1.05 usec per loop


Если кратко, то: Если ты знаешь, что ключ присутствует, get медленнее.
Если не знаешь -- get в помощь.

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

Конечно, это только касается случая, когда значение по умолчанию
простое. Если же оно содержит нечто, более "тяжелое" чем except,
то try/except лучше, поскольку в этом случае, значение
не конструируется для каждого элемента заново. Так что, если
сомневаешься и код критичный по скорости -- сделай замеры скорости
с timeit, вместо того чтобы понимать как аксиому "исключения
медленны" или "[] быстры"


Утечки переменных

Для некоторых конструкций, в CPython существуют <<утечки>> переменных
в локальное пространство имен.

def f(s):
while True:
try:
some_func_that_throws_exception()
except Exception, e:
# e exists in this namespace now.
pass
# some other code here...


Для кода выше, e "протекает" в пространство имен f --
это неиспользуемая память, на которую ссылаются и она будет
сохраняться, пока цикл не закончится.

Python "пропускает" переменные в локальное пространство имен -- будь
внимателен с этим и явно удаляй неиспользуемые ссылки, когда имеешь
дело с большими объектами, особенно с исключениями.

class c:
d = {}
for x in range(1000):
d[x] = x


Вышеприведенный класс надуман, но ситуация в том, что c.x теперь
существует --
x из цикла "протекает" в пространство имен класса и остается там.
Не оставляй неиспользуемые переменные в пространстве имен класса.
Переменные, которые "протекают" из циклов for обычно не существенны,
лишь имей их ввиду -- особенно, если переменная ссылается на большой
объект (это сохранит память).

Итак... циклы for "текут", списочные выражения "текут",
вспомогательные переменные в try/except тоже "текут".


(x)range

Используй xrange всегда, за исключением ситуации, когда тебе нужно
сгенерировать и сохранить результат range.

$ python -m timeit 'for x in range(10000): pass'
100 loops, best of 3: 2.01 msec per loop
$ python -m timeit 'for x in xrange(10000): pass'
1000 loops, best of 3: 1.69 msec per loop


Удаление из списка

Удаление элементов из списка -- достаточно медленная операция,
особенно если бОльшая часть списка остается. Если тебе действительно
нужно оставлять бОльшую часть списка при удалении -- модуль deque (на
самом деле, пакет collections, появился в python-2.4 -- прим. j2a)
тебе поможет.

$ python -m timeit $'l=range(1000);i=0;nwhile i < len(l):ntif l[i]!="asdf":del l[i]ntelse:i+=1'
100 loops, best of 3: 4.12 msec per loop
$ python -m timeit $'l=range(1000);nfor i in xrange(len(l)-1,-1,-1):ntif l[i]!="asdf":del l[i]'
100 loops, best of 3: 3 msec per loop
$ python -m timeit 'l=range(1000);l=[x for x in l if x == "asdf"]'
1000 loops, best of 3: 1 msec per loop


Так что, удалять элементы из списка -- самый медленный способ. Самый
быстрый способ -- списковые выражения. Т.о., если есть возможность
не удалять элементы -- не удаляй их.


None

Если ты проверяешь на None, обрати внимание на оператор 'is'. Он не
использует протокол равенства и сравнивает непосредственно указатели.

$ python -m timeit '1 != None'
1000000 loops, best of 3: 0.721 usec per loop
$ python -m timeit '1 is not None'
1000000 loops, best of 3: 0.343 usec per loop


Устаревшие модули

Не используй types, используй isinstance (не по причинам оптимизации,
types -- само по себе плохо)
Не используй strings. Есть исключения, но лучше используй методы.
Не используй модуль stats модуль только для того, чтобы получить
аттрибут stat, например:

import stats
l=os.stat("asdf")[stat.ST_MODE]

# также может быть сделано при помощи: (и это более "правильно")
l=os.stat("asdf").st_mode


Обработка исключений

Знай возбуждаемые исключение и перехватывай только нужные из них.

try:
blah
except Exception:
blah2


Вышеприведенный код ловит все исключения, в том числе
и KeyboardInterrupt, и SystemExit. Это означает, что <<кривая>>
обработка исключений приводит к тому, что программа не правильно
обрабатывает ctrl+c.
Так что перехватывай только нужные тебе исключения.


Кортежи против списков

Первый неизменяемый, второй -- изменяемый.
Списки занимают больше памяти чем потом используют реально (в
большинстве случаев это хорошо).
Если ты генерируешь большое количество последовательностей, которые
не будут изменяться, используй кортежи.


Неизменяемые объекты

Копирование неизменяемых (например, кортежи, строки) объектов -- тупо.
Например:
copy.copy1,2,3 -- это тупо; никто не сделает такую явную ошибку, но в
большом коде народ их делает (к примеру, пытается скопировать строку,
используя [:], результатом будет сама строка, поскольку
она не изменяема).

Их нельзя поменять, так что и нечего пытаться их скопировать.


__del__

Метод __del__ имеет один досадный побочный эффект: он блокирует сборку
мусора, когда объект вызывается в цикле -- интерпретатор не знает,
что __del__ удалит ссылку на объект, так что он не в курсе (в общем
случае) как остановить цикл.

Так что... если используешь метод __del__, убедись что объект
не зацикливается (не важно, аккуратно ли используются данные,
или используется weakref)


Узкое место

Обычно не python медленный, а твой алгоритм.

l = []
for x in data_generator():
if x not in l:
l.append(x)


Для этого кода в лучшем случае O(1) (например, когда генератор выдает
одни нули). Худший случай -- O(N^2).

l=set()
for x in data_generator():
if x not in l:
l.add(x)


Лучший/худший случай теперь константа (в действительности, не совсем,
из-за потенциального расширения множества, но в данном случае
это можно опустить).

Далее, первый цикл в действительности вызывает __eq__ для сравнения
x для каждого элемента, так что здесь возможна ощутимая потеря
производительности в случае сложного объекта.

А второй цикл вызывает __hash__ для каждого x один раз. Чувствуешь
разницу?

Технически, второй цикл всё еще остается неэффективным:

l=set(data_generator())


проще и быстрее.
И реальные цифры для народа, который не видит, насколько всё плохо:

$ python -m timeit $'l=[]nfor x in xrange(1000):ntif x not in l:l.append(x)'
10 loops, best of 3: 74.4 msec per loop
$ python -m timeit $'l=set()nfor x in xrange(1000):ntif x not in l:l.add(x)'
1000 loops, best of 3: 1.24 msec per loop
$ python -m timeit 'l=set(xrange(1000))'
1000 loops, best of 3: 278 usec per loop


Небольшая разница, да?

Это не означает, что множества (set) автоматически лучше везде,
это не так: например, для одной операции поиска в диапазоне, косвенные
затраты (оверхед) гораздо больше, чем для линейного поиска.

$ python -m timeit -s 'l=range(50)' $'if 1001 in set(l): pass'
100000 loops, best of 3: 12.2 usec per loop
$ python -m timeit -s 'l=range(50)' $'if 1001 in l: pass'
10000 loops, best of 3: 7.68 usec per loop
570 Прочтений •  [Советы по увеличению производительности Python скриптов (python optimization tune speed)] [08.05.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Советы по увеличению производительн... Ukraine Vova 08.05.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка