Архив рубрики: Python

Руководство по анализу производительности скриптов на Python (Перевод)

Поскольку не каждая программа на Python требует тщательного анализа производительности, отрадно знать, что когда такая необходимость возникает у Вас всё же есть отличные инструменты для этой цели.
Анализ производительности программы состоит в поиске ответа на четыре вопроса:
  1. Как быстро выполняется программа?
  2. Какое место определяет скорость её выполнения?
  3. Как много используется памяти?
  4. Где утечки памяти?
Посмотрим, что же у нас есть для ответа на эти вопросы.

Грубая оценка времени выполнения

Давайте начнём  простого и быстрого способа оценки времени выполнения нашего кода: старой доброй утилиты UNIX time:
$ time python yourprogram.py

real 0m1.028s
user 0m0.001s
sys 0m0.003s

Объяснение этого результата можно найти в этой статье. В кратце:

  • real — время от запуска программы до её завершения
  • user — время процессора, затраченное вне ядра
  • sys — время процессора, затраченное в ядре
Таким образом, сложив user и sys, Вы можете оценить сколько процессорного времени потребовалось вашей программе вне зависимости от загрузки системы.
Если эта сумма сильно больше чем реально затраченное время, тогда можно предположить, что проблемы с производительностью вашей программы связаны с ожиданием ввода / вывода.

Более точная оценка времени с использованием менеджера контекста

Вот маленький модуль, который окажет нам бесценную пользу в измерении времени выполнения:
timer.py
import time

class Timer(object):
def __init__(self, verbose=False):
self.verbose = verbose

def __enter__(self):
self.start = time.time()
return self

def __exit__(self, *args):
self.end = time.time()
self.secs = self.end - self.start
self.msecs = self.secs * 1000 # millisecs
if self.verbose:
print 'elapsed time: %f ms' % self.msecs

Для того, чтобы его использовать, мы должны обернуть тот кусок кода, время выполнения которого Вы хотите замерить в полученный менеджер контекста. Он запускает измерение времени в начале выполнения блока кода и завершает после выполнения. Вот пример использования этого модуля:
from timer import Timer
from redis import Redis
rdb = Redis()

with Timer() as t:
rdb.lpush("foo", "bar")
print "=> elasped lpush: %s s" % t.secs

with Timer as t:
rdb.lpop("foo")
print "=> elasped lpop: %s s" % t.secs
Я обычно сохраняю вывод в файл для того, чтобы отслеживать изменения производительности с ростом программы.

Построчный тайминг и частота выполнения с
помощью profiler

У Robert Kern есть замечательный проект под названием line_profiler, которым я часто пользуюсь для того, чтобы понять, как быстро выполняется каждая строчка моего кода. Чтобы его использовать — Вам надо сперва установить его при помощи pip:
$ pip install line_profiler

После установки можно использовать модуль “line_profiler” или скрипт “kernprof.py”.

Чтобы использовать этот инструмент для начала Вам надо изменить свой код, обернув функцию, производительность которой Вы хотите измерить, в декоратор @profile. Не беспокойтесь, для этого Вам не надо будет ничего импортировать. Скрипт kernprof.py автоматически добавит всё, что нужно в ваш скрипт в процессе выполнения.
primes.py
@profile
def primes(n):
if n==2:
return [2]
elif n<2:
return []
s=range(3,n+1,2)
mroot = n ** 0.5
half=(n+1)/2-1
i=0
m=3
while m <= mroot:
if s[i]:
j=(m*m-3)/2
s[j]=0
while j<half:
s[j]=0
j+=m
i=i+1
m=2*i+3
re turn [2]+[x for x in s if x]
primes(100)
После того, как Вы настроили свой скрипт — используйте usekernprof.py для его запуска.
$ kernprof.py -l -v fib.py
Опция -l указывает kernprof'у добавить декоратор @profile во встроенную область видимости вашего скрипта, а -v указывает kernprof указать информацию о тайминге после выполнения скрипта. Вывод должен выглядеть так:
Wrote profile results to primes.py.lprof
Timer unit: 1e-06 s

File: primes.py
Function: primes at line 2
Total time: 0.00019 s

Line # Hits Time Per Hit % Time Line Contents
==============================================================
2 @profile
3 def primes(n):
4 1 2 2.0 1.1 if n==2:
5 return [2]
6 1 1 1.0 0.5 elif n<2:
7 return []
8 1 4 4.0 2.1 s=range(3,n+1,2)
9 1 10 10.0 5.3 mroot = n ** 0.5
10 1 2 2.0 1.1 half=(n+1)/2-1
11 1 1 1.0 0.5 i=0
12 1 1 1.0 0.5 m=3
13 5 7 1.4 3.7 while m <= mroot:
14 4 4 1.0 2.1 if s[i]:
15 3 4 1.3 2.1 j=(m*m-3)/2
16 3 4 1.3 2.1 s[j]=0
17 31 31 1.0 16.3 while j 18 28 28 1.0 14.7 s[j]=0
19 28 29 1.0 15.3 j+=m
20 4 4 1.0 2.1 i=i+1
21 4 4 1.0 2.1 m=2*i+3
22 50 54 1.1 28.4 return [2]+[x for x in s if x]

Ищите строки с большим значением hits или time. Это те места, где скрипт можно оптимизировать.

Какое количество памяти мы используем?

Теперь, узнав время выполнения нашего кода, давайте посмотрим на объем используемой им памяти. К счастью для нас, Fabian Pedregosa сделал хороший профилировщик памяти.
Для начала установим его при помощи pip:
$ pip install -U memory_profiler
$ pip install psutil
(Установка пакета psutil сильно ускорит работу memory_profiler).
Как и line_profiler, memory_profiler требует декорирования интересующей вас функции при помощи декоратора @profile:
@profile
def primes(n):
...
...
Для того, чтобы посмотреть сколько памяти использует ваша функция запустите скрипт так:
$ python -m memory_profiler primes.py
Вот пример вывода отчёта после завершения работы скрипта:
Filename: primes.py

Line # Mem usage Increment Line Contents
==============================================
2 @profile
3 7.9219 MB 0.0000 MB def primes(n):
4 7.9219 MB 0.0000 MB if n==2:
5 return [2]
6 7.9219 MB 0.0000 MB elif n<2:
7 return []
8 7.9219 MB 0.0000 MB s=range(3,n+1,2)
9 7.9258 MB 0.0039 MB mroot = n ** 0.5
10 7.9258 MB 0.0000 MB half=(n+1)/2-1
11 7.9258 MB 0.0000 MB i=0
12 7.9258 MB 0.0000 MB m=3
13 7.9297 MB 0.0039 MB while m <= mroot:
14 7.9297 MB 0.0000 MB if s[i]:
15 7.9297 MB 0.0000 MB j=(m*m-3)/2
16 7.9258 MB -0.0039 MB s[j]=0
17 7.9297 MB 0.0039 MB while j 18 7.9297 MB 0.0000 MB s[j]=0
19 7.9297 MB 0.0000 MB j+=m
20 7.9297 MB 0.0000 MB i=i+1
21 7.9297 MB 0.0000 MB m=2*i+3
22 7.9297 MB 0.0000 MB return [2]+[x for x in s if x]

Куда утекает память?

Интерпретатор сPython использует счётчик ссылок
для управления памятью. То есть каждый объект содержит счётчик, который увеличивается на 1, когда ссылка на этот объект сохраняется где-то, и уменьшается при удалении этой ссылки. Когда счётчик становится равным нулю интерпретатор cPython знает, что объект больше не используется и можно освободить занимаемую им память.
Утечки памяти могут возникать в вашей программе если где-то сохраняется ссылка на какой-то уже не используемый объект.
Самый быстрый способ поиска таких утечек памяти — использовать восхитительный инструмент objgraph написанный Marius Gedminas. Этот инструмент помогает увидеть количество объектов в памяти и обнаружить разные места в коде, которые содержат ссылки на эти объекты.
Для начала установите objgraph:
pip install objgraph
После этого вставьте в ваш код выражения для вызова отладчика:
import pdb; pdb.set_trace()

Статистика использования объектов

В процессе выполнения Вы можете посмотреть на 20 наиболее используемых объектов в вашей программе:
(pdb) import objgraph
(pdb) objgraph.show_most_common_types()

MyBigFatObject 20000
tuple 16938
function 4310
dict 2790
wrapper_descriptor 1181
builtin_function_or_method 934
weakref 764
list 634
method_descriptor 507
getset_descriptor 451
type 439

Какие объекты были добавлены и удалены?

Для того, чтобы посмотреть, какие объекты были удалены или добавлены между двумя точками сделайте так:
(pdb) import objgraph
(pdb) objgraph.show_growth()
.
.
.
(pdb) objgraph.show_growth() # this only shows objects that has been added or deleted since last show_growth() call

traceback 4 +2
KeyboardInterrupt 1 +1
frame 24 +1
list 667 +1
tuple 16969 +1

Что ссылается на этот «утёкший объект»?

Давайте для примера возьмём простой скрипт:
x = [1]
y = [x, [x], {"a":x}]
import pdb; pdb.set_trace()
Чтобы увидеть, что ссылается на переменную x, вызовите функцию objgraph.show_backref():
(pdb) import objgraph
(pdb) objgraph.show_backref([x], filename="/tmp/backrefs.png")
На выводе Вы получите PNG изображение, сохранённое в/tmp/backrefs.png:
back refrences
Квадрат внизу с красными буквами это и есть интересующий нас объект. Мы можем увидеть, что на него ссылается x один раз и список y три раза. Если x is вызывает утечки памяти, можно воспользоваться этим методом чтобы найти эти забытые ссылки.
Ещё раз: objgraph позволяет нам:
  • посмотреть N популярных объектов в памяти вашей программы
  • посмотреть какие объекты были удалены и добавлены за какой-то период времени
  • посмотреть все ссылки на заданный объект в нашем скрипте

Эффективность против точности

В этом посте я показал несколько инструментов анализа производительности программ на Python. Вооружённые этими инструментами Вы можете получить всю нужную информацию для поиска утечек памяти и бутылочных горлышек в вашем скрипте.
Как и во многих других случаях, точность анализа и скорость его выполнения зависят одно от другого. Так что если Вы сомневаетесь — используйте минимальное решение, удовлетворяющее ваши нужды.
Ссылки

Источник

Автор: Ishayahu Lastov

Релиз CFFI 0.2

Вышел релиз CFFI 0.2 (а вскоре ожидается и 1.0). CFFI это способ вызова C из Python. Этот релиз касается только CPython 2.6 или 2.7. Поддержка PyPy будет в ветке ffi-backend, но она ещё не реализована.Поддержку CPython 3.x сделать легко, но нужна для этого помощь.
Пакет доступен на bitbucket и хорошо документирован. Кроме того Вы можете просто установить его из python package index: pip install cffi
В этом релизе:
  • Многочисленные небольшие изменения и поддержка большего количества Си-измов
  • Большая новость: поддержка installing packages которые используют ffi.verify() на машина без компилятора C. Возможно это снимает последнее ограничение, которое удерживало людей от использования CFFI.
  • Некоторые небольшие изменения:
    • отображение между 'wchar_t' и Python юникодом
    • введение ffi.NULL
    • возможно более ясное API для ffi.new(): т.е. для того, чтобы выделить единичный int и получить указатель на него, используйте ffi.new(«int *») вместо старого ffi.new(«int»)
    • и, конечно, множество маленьких исправлений
  • CFFI использует pkg-config при наличии для собственной установки. Это помогает определить место libffi на современных Linux. Поддержка Mac OS/X тоже доступна (смотрите детальные инструкции по установке). Win32 должен работать из коробки. Win64 реально ещё не тестирован.

    Автор: Ishayahu Lastov

    Сериал "Python PDF" – Обзор metaPDF (Перевод)

    Исследуя библиотеки для работы с PDF на Python, я наткнулся на ещё один маленький проект с названием metaPDF. Судя по указанному на сайте этого проекта, metaPDF это лёгкая библиотека для Python, оптимизированная для получения и вставки метаданных, по сути это легковесная обёртка вокруг замечательной библиотеки pyPdf. Я не уверен в том, что эта библиотека будет полена с учётом возможности работы с самой библиотекой pyPdf, так что давайте посмотрим на неё в деле.

    Получение и использование metaPDF

    Процесс установки metaPDF достаточно прост, особенно если использовать easy_install или pip. После этого давайте запустим маленький скрипт для того, чтобы посмотреть, как он работает. Вот пример, основанный на информации с github:
    from metapdf import MetaPdfReader
     
    pdfOne = r'C:UsersmdriscollDocumentsreportlab-userguide.pdf'
    x = MetaPdfReader()
    metadata = x.read_metadata(open(pdfOne, 'rb'))
    print metadata
    Я запускаю скрипт к руководству по Reportlab. Обратите внимание, что в оригинале есть опечатка — там используется “read” для открытия файла. Я полагаю, что он не будет работать, пока Вы сам не откроете файл. В любом случае, вот результат работы этого скрипта:
    {'/ModDate': u'D:20120629155504', '/CreationDate': u'D:20120629155504', '/Producer': u'GPL Ghostscript 8.15', '/Title': u'reportlab-userguide.pdf', '/Creator': u'Adobe Acrobat 10.1.3', '/Author': u'mdriscoll'}
    Я правда не знаю, каким образом изменилось поле «автор» в документе, но я определённо им не являюсь. Так же я не понимаю, откуда в названии ключей берутся прямые слешы. Судя по исходникам, это всё, что может делать эта библиотека. Разочарованы?  Может быть, если мы проявим внимание к этой библиотеке, автор добавит ей функциональности…

    Автор: Ishayahu Lastov

    Python 101: Загрузка файла при помощи ftplib (Пароль)

    Есть несколько способов загрузки файла из интернета при помощи Python. Одним из самых популярных способов является подключение к FTP серверу и скачивание файла. Этим мы и сейчас займёмся. Всё, что нам понадобится — стандартная установка Python. В неё включена библиотека ftplib, которой нам вполне хватит.

    Скачиваем!

    Скачать файл очень просто:
    # ftp-ex.py
     
    import os
    from ftplib import FTP
     
    ftp = FTP("www.myWebsite.com", "USERNAME", "PASSWORD")
    ftp.login()
    ftp.retrlines("LIST")
     
    ftp.cwd("folderOne")
    ftp.cwd("subFolder") # или ftp.cwd("folderOne/subFolder")
     
    listing = []
    ftp.retrlines("LIST", listing.append)
    words = listing[0].split(None, 8)
    filename = words[-1].lstrip()
     
    # скачиваем файл
    local_filename = os.path.join(r"c:myfolder", filename)
    lf = open(local_filename, "wb")
    ftp.retrbinary("RETR " + filename, lf.write, 8*1024)
    lf.close()
    Давайте разбираться. Во-первых, нам надо подключиться к FTP серверу, так что необходимо передать URL сервера, логин и пароль. Если же Вы используете анонимный FTP сервер — то последние два аргумента можно опустить. Команда retrlines(“LIST”) выдаёт листинг папки. Команда cwd изменяет рабочую папку (“change working directory”), которую Вы используете для перехода в нужную для Вас папку. В следующей части показано как достаточно глупым способом получить имя файла. Вы можете использовать os.path.basename для тех же целей. Последняя часть показывает, собственно, как скачать файл. Обратите внимание, что файл открывается в режиме “wb” (двоичная запись). “8*1024″ — размер блоков для загрузки, хотя Python достаточно умён, чтобы выбрать подходящий размер.

    Обратите внимание: Эта статья основана на документации Python для модуля ftplib и этот скрипт можно найти в папке с установленным Python: Tools/scripts/ftpmirror.py.

    Домашнее чтение

    Автор: Ishayahu Lastov

    Знакомимся с virtualenv (Перевод)

    Виртуальное окружение может быть очень полезно для тестирования программ. Ian Bicking создал проект  virtualenv, который и является инструментом для создания изолированного окружения Python. Вы можете использовать эти окружения для проверки новых версий ваших программ, новых версий пакетов, которые Вы используете или просто в качестве песочницы для новых пакетов. Кроме того, Вы можете использовать virtualenv в качестве рабочего места в случаях, если Вы не можете копировать файлы в site-packages по какой-либо причине. Когда Вы создаёте виртуальное окружение при помощи virtualenv, он создаёт папку и копирует Python в неё с папкой site-packages и несколькими другими. Кроме того устанавливается pip. Пока активно ваше виртуальное окружение оно используется как обычный Python. Когда же Вам оно перестаёт быть нужным — Вы просто удаляете папку. И всё. Или можете продолжать её использовать для работы.
    В этой статье мы потратим немного времени для знакомства с virtualenv.

    Установка

    Для начала понадобится установить virtualenv. Вы можете использовать для этого pip или easy_install или просто загрузить файл virtualenv.py с их сайта и просто использовать его. На данный момент предположим, что ваша папка Python находится в системном пути поиска и Вы можете использовать  virtualenv в командной строке.

    Создаём виртуальное окружение

    Для создания песочницы выполните следующую команду:

    python virtualenv.py FOLDER_NAME
    Где FOLDER_NAME — это имя папки, которую Вы будете использовать в качестве песочницы. На моём компьютере Windows 7, я добавил папку C:Python26Scripts в путь поиска так что я могу использовать команду virtualenv.py FOLDER_NAME без слова python. Если Вы не укажете имя папки, то получите в ответ список опций, которые можно передать скрипту. Давайте создадим проект с названием sandbox. Как его использовать? Для начала надо его активировать:
    На POSIX системах Вам нужно source bin/activate тогда как на Windows Вам нужна команда путь_к_песочницеScriptsactivate. Давайте пройдём через эти шаги. Песочницу создадим на рабочем столе:

    C:UsersmdriscollDesktop>virtualenv sandbox
    New python executable in sandboxScriptspython.exe
    Installing setuptools................done.
    Installing pip...................done.

    C:UsersmdriscollDesktop>sandboxScriptsactivate
    (sandbox) C:UsersmdriscollDesktop>

    Вы можете заметить что как только Вы активировали ваше виртуальное окружение, Вы увидите в приглашении командной строки префикс с названием папки для вашей песочницы (в нашем случае — “sandbox”). Так Вы можете определить, используется ли песочница или нет. После этого Вы можете использовать pip для установки пакетов в ваше виртуальное окружение. Когда закончите работать просто вызовите команду декативации для того, чтобы выйти из виртуального окружения.
    Есть несколько флагов, которые Вы можете передать скрипту virtualenv при создании виртуального окружения. Например, можно использовать –system-site-packages для переноса пакетов из установки Python. Если Вы хотите использовать distribute вместо setuptools, можете использовать флаг –distribute.
    virtualenv так же предоставляет способ просто установить библиотеки, но использовать их при помощи стандартного Python. Согласно документации, для этого Вам надо лишь написать специальный скрипт. Подробнее смотрите тут.
    Кроме того, есть экспериментальный флаг –relocatable который можно использовать для того, чтобы сделать папку переместимой. Однако, на данный момент, это не работает на Windows.
    Наконец, есть флаг –extra-search-dir, который можно использовать, чтобы сохранить ваше виртуальное окружение оффлайн. Это позволяет Вам добавить папку в путь поиска, по которому pip или easy_install будет искать пакеты для установки, даже если у Вас нет доступа к интернету.

    Итог

    К этому времени Вы должны уметь сами использовать virtualenv. Есть несколько других проектов, которые теперь могут Вам пригодиться. Есть библиотека Doug Hellman’а virtualenvwrapper, которая облегчает создание, удаление и управление виртуальных окружений. Есть zc.buildout, который похож на virtualenv и является его конкурентом. Я рекомендую посмотреть их и изучить — они могут помочь Вам в вашей работе.

    Автор: Ishayahu Lastov

    Python 101: pip –замена для easy_install (Перевод)

    Pip Installs Python или pip — это инструмент для установки и управления пакетами Python, многие из которых расположены на Python Package Index (PyPI). Он является альтернативой easy_install. В этой статье мы потратим немного времени на то, чтобы посмотреть, как он работает и как он может помочь нам в нашей работе с Python.

    Установка

    Для работы Вам потребуется distribute или setuptools. Если Вы работаете с Python 3, тогда ваш единственный выбор — distribute, так как setuptools на данный момент ещё не портирован на эту версию. На сайте  pip есть установщик, так то вы можете использовать get-pip.py или просто перейти на PyPI и скачать исходники.
    Как Вы уже знаете, для установки большинства модулей из исходников необходимо распаковать полученный архив, перейти в его папку и запустить в ней команду “python setup.py install”. Обратите внимание, что для установки могут понадобиться права администратора. На сайте pip рекомендуется использовать pip в virtualenv, поскольку в таком случае он устанавливается автоматически, “не требует прав администратора и не изменяет вашу установку Python”. Но это на ваш выбор.

    использование pip

    Чаще всего pip используют для установки, обновления или удаления пакетов. Все эти процедуры описаны на официальном сайте, но мы по ним всё равно пройдёмся. Так как мы упомянули про virtualenv, давайте и установим его при помощи pip:

    pip install virtualenv
    Если Вы запустите эту команду в терминале, Вы увидите что-то вроде этого:

    Downloading/unpacking virtualenv
    Downloading virtualenv-1.7.2.tar.gz (2.2Mb): 2.2Mb downloaded
    Running setup.py egg_info for package virtualenv
    warning: no previously-included files matching '*' found under directory 'do
    cs_templates'
    warning: no previously-included files matching '*' found under directory 'do
    cs_build'
    Installing collected packages: virtualenv
    Running setup.py install for virtualenv
    warning: no previously-included files matching '*' found under directory 'do
    cs_templates'
    warning: no previously-included files matching '*' found under directory 'do
    cs_build'
    Installing virtualenv-script.py script to C:Python26Scripts
    Installing virtualenv.exe script to C:Python26Scripts
    Installing virtualenv.exe.manifest script to C:Python26Scripts
    Installing virtualenv-2.6-script.py script to C:Python26Scripts
    Installing virtualenv-2.6.exe script to C:Python26Scripts
    Installing virtualenv-2.6.exe.manifest script to C:Python26Scripts
    Successfully installed virtualenv
    Cleaning up...
    Похоже, что работает. Обратите внимание, что pip загружает пакет ДО начала установки, чего не делает easy_install (за другими отличиями обраща