python и его тормоза
DESCRIPTION
Доклад на pycamp kiev.TRANSCRIPT
Python и его тормозаМожно ли заставить питон работать быстро
Александр Шигин, [email protected]
Rambler, 2010
О чем я буду говорить
краткий рассказ о времени;простые примеры;быстрые простые примеры;глупые примеры;и еще глупости.
Время
Первый пример — real time.
$ time sleep 5
real 0m5.006suser 0m0.004ssys 0m0.008s
Время
Второй пример — user time.
$ time openssl rand \> -out /dev/null 9000000
real 0m0.421suser 0m0.388ssys 0m0.004s
Время
Третий пример — system time.
$ time LANG=C dd if=/dev/zero \> of=/dev/null count=900000 bs=1900000+0 records in900000+0 records out900000 bytes (900 kB) copied, 0.413783 s, 2.2 MB/s
real 0m0.417suser 0m0.096ssys 0m0.312s
Время
Для процесса есть три времени:real, CPU, system.
обычно нас интересует real time;но если мы запускаем многопараллельных задач мы можемупереться в CPU (user) time;системное время в основномзависит от количествасистемных вызовов.
Чуть-чуть чисел
вызов функции по указателю (нс)C Python bi Python pure4 140 270 [2.5]
130 260 [2.6]сумма массива из 400 целых чисел
C Python bi Python pure590 16 550 49 300 [2.5]
5 700 45 150 [2.6]
Чуть-чуть числа про объекты
создание чистого python объекта210 нс;создание python объекта спустым __slots__ 120 нс;создание embedded pythonобъекта 150 нс.
Создание объектов
создание кортежа из двухэлементов 31 нс;создание класса с двумя полями690 нс;распаковка кортежа 130 нс;доступ к полю кортежа 105 нс;доступ к полю объекта 110 нс.
Маленький пример
Как лучше хранить неизменяемыепары чисел? Учебный пример —нахождение центра масс.
кортеж из трех чисел: x, y,масса;класс с тремя полями;словарь.
Маленький пример: класс
class Po s i t i o n :def __init__( s e l f , x , y , mass ) :
s e l f . x = xs e l f . y = ys e l f . mass = mass
x = [ P o s i t i o n (0 , 0 , 10) ,P o s i t i o n (1 , 1 , 13) ,P o s i t i o n (1 , 2 , 5 ) ]
Маленький пример: класс
def get_cente r ( l s t ) :rx , ry , mass = 0 . 0 , 0 . 0 , 0 . 0for i t em in l s t :
r x += item . x ∗ i t em . massr y += item . y ∗ i t em . massmass += item . mass
return Po s i t i o n ( r x /mass , r y /mass ,mass )
Результат: 3900 нс
Маленький пример: объекты
новый класс ≈ 4100 нс;в 2.5 новый класс ≈ 4750 нс.__slots__ 2.6 ≈ 4050 нс, 2.5 —4500 нс.
Маленький пример: кортеж
def get_cente r ( l s t ) :rx , ry , rm = 0 . 0 , 0 . 0 , 0 . 0for x , y , mass in l s t :
r x += x ∗ massr y += y ∗ massrm += mass
return ( r x /rm , r y /rm , rm)
Результат: 2200 нс
Маленький пример: словарь
def get_cente r ( l s t ) :rx , ry , rm = 0 . 0 , 0 . 0 , 0 . 0for i t em in l s t :
r x += item [ ’ x ’ ] ∗ i t em [ ’ mass ’ ]r y += item [ ’ y ’ ] ∗ i t em [ ’ mass ’ ]rm += item [ ’ mass ’ ]
return ( r x /rm , r y /rm , rm)
Результат: 2600 нс
Маленький пример
Итог: используйте кортежи.
Меряем 2.5 2.6старый класс 4050 3900новый класс 4750 4130со слотами 4490 4050
кортежи 2250 2210словарь 2700 2630
Глупый пример
Смешные числа Фибоначи. Каклучше хранить текущую паручисел?
В этом примере мы будем изменятьпару.
Глупый пример: класс
Первый вариант.
class F :def __init__( s e l f , x , y ) :
s e l f . x = xs e l f . y = y
Глупый пример: класс
Проверяем:
x = F(1 , 1)t = x . yx . y = x . x + x . yx . x = t# так 4 разаРезультат: 2830 нс
Глупый пример: классы
x.x, x.y = x.y, x.x + x.yнемного медленнее;надо повторяться про новыеклассы?
Глупый пример: кортеж
Проверяем:
x = 1 , 1x = x [ 1 ] , x [ 0 ] + x [ 1 ]# так 4 разаРезультат: 1410 нс
Глупый пример: словарь
Проверяем:
x = d i c t ( x=1, y=1)t = x [ ’ y ’ ]x [ ’ y ’ ] = x [ ’ x ’ ] + x [ ’ y ’ ]x [ ’ x ’ ] = t# так 4 разаРезультат: 2500 нс
Глупый пример
Итог: даже в этом случаеиспользуйте кортежи.
Меряем 2.5 2.6старый класс 2910 2835новый класс 4630 3468со слотами 4065 3212
кортежи 1550 1410словарь 2810 2550
Замыкания
вызов замыкания 145 нс;возврат значения из замыкания166 нс;создание замыкания 380 нс;
Первый пример
Создадим класс, экземплярыкоторого будут возвращать свойаргумент, увеличенный на некоечисло, которое мы передадим присоздании класса.
x = X(14)x (15) # −> 29
Первый пример — старые классы
class X:def __init__( s e l f , x ) :
s e l f . x = xdef __call__( s e l f , x ) :
return s e l f . x + xx = X( 1 ) ; s = 0
# проверяемs += x (12)
Результат: 650 нс
Первый пример — новый класс
class X( ob j e c t ) :def __init__( s e l f , x ) :
s e l f . x = xdef __call__( s e l f , x ) :
return s e l f . x + xx = X( 1 ) ; s = 0
# проверяемs += x (12)
Результат: 650 → 404 нс
"Старые" классы
typedef struct {PyObject_HEADPyObject ∗ c l_base s ;PyObject ∗ c l_d i c t ;PyObject ∗cl_name ;
PyObject ∗ c l_get / s e t / d e l a t t r ;} PyC la s sOb j ec t ;
"Новые" классы
typedef struct _typeob j ec t {PyObject_VAR_HEAD
/∗ s k i p f i e l d s ∗/hash func tp_hash ;t e r n a r y f u n c t p_ca l l ;
/∗ s k i p f i e l d s ∗/} PyTypeObject ;
Первый пример — странный результат
class X:def __init__( s e l f , x ) :
s e l f . x = xdef c a l l ( s e l f , x ) :
return s e l f . x + xx = X( 1 ) ; s = 0
# проверяемs += x . c a l l (12)
Результат: 650 → 404 → 380 нс
Первый пример — замыкание
def X( x ) :def i n n e r ( y ) :
return x+yreturn i n n e r
x = X( 1 ) ; s = 0
# проверяемs += x (12)
Результат: 650 → 404 → 380 → 228 нс
Итераторы/генераторы
просчитать генератором до100 — 31 700 нс;просчитать итератором до100 — 82 200 нс;itertools.count — 7 400 нс;xrange(999) — 7 500 нс.
Проверка через list(islice(iterable, 100)).
Чуть-чуть реальный пример
Выделить тройки (uid, login,домашняя директория) из/etc/passwd.
Это достаточно часть первого этапаобработки больших файлов.
passwd: генератор
def f unc ( i a b l e ) :for l i n e in i a b l e :
x = l i n e . s p l i t ( ’ : ’ )y i e l d i n t ( x [ 2 ] ) , x [ 0 ] , x [ 5 ]
passwd: итератор
class f unc :def __init__( s e l f , i a b l e ) :
s e l f . i a b l e = i a b l e
def __iter__( s e l f ) : return s e l f
def next ( s e l f ) :l i n e = s e l f . i a b l e . nex t ( )x = l i n e . s p l i t ( ’ : ’ )return i n t ( x [ 2 ] ) , x [ 0 ] , x [ 5 ]
passwd: imap
def f unc ( l i n e ) :x = l i n e . s p l i t ( ’ : ’ )return i n t ( x [ 2 ] ) , x [ 0 ] , x [ 5 ]
passwd
Вывод: генераторы — это не толькоудобно, но и быстро.
2.5 2.6генератор 125 626 148 311итератор 181 100 210 167
imap 146 084 167 703
Некоторые общие вещи
Некоторые способы ускоритьвстроенный код:
interning;proxy-object;object cache.
string interning
Создание объекта требуетдополнительных действий.Если мы используем одну и туже строку постоянно, гораздобыстрее создать один раз pythonстроку, а потом передавать ее вpython API.Эта техника подходит не толькодля строк.
string interning: пример
while ( ( i tem = PyIter_Next ( i t e r a t o r ) )!= NULL) {
PyObject ∗ f i e l d = \PyObjec t_GetAt t rSt r i ng (item , " th " ) ;
// PyObject ∗ f i e l d = \// PyObject_GetAttr (// item , i n t e rned_th ) ;. . .Py_DECREF( i tem ) ;
}
string interning: итог
В вырожденных случаях C кодбудет медленнее чем Python код.
python код 14 830C-код, C строка 16 636
С-код, встроенная строка 8 862
proxy object
Если у вас есть развесистаяструктура «нативных»объектов, не надо для них всехсоздавать python объекты.Создание объектов потребованию экономит не тольковремя создания родительскогообъекта, но и память.
object cache
Если объекты неизменяемые иодни объекты встречаются чащечем другие, можно возвращатьзаранее созданные объекты.Хорошие примеры: целые числа,короткие строки, узел дерева бездетей, единичные массивы.
object cache
создание 50 тысяч int(5) — 450мкс (9 нс);создание 50 тысяч int(5000) —760 мкс (15 нс);кеш объектов в данном случаедал прирост в 40%.
Global Interpretor Lock
Все видели картинку про два CPUbound треда?
Картинка нагло украдена из статьи The Python GILVisualized http://www.dabeaz.com/
Global Interpretor Lock
Во время работы встроенныхфункций можно разблокироватьGIL, если мы не используемPython API.Хорошие примеры: py-lxml.
Python API
Сложно.Запутанно.Считать ссылки самому:Py_DECREF, Py_INCREF
Брррр...
Ужас
wh i l e ( ( i tem = PyIter_Next ( i t e r a t o r ) ) \!= NULL) {
PyObject ∗ f i e l d = PyObject_GetAttr (item , i n t e rned_th ) ;
i f ( f i e l d == NULL) {Py_DECREF( r e s u l t ) ;Py_DECREF( i tem ) ;r e t u r n NULL ;
}PyObject ∗nw = PyNumber_Add(
r e s u l t , f i e l d ) ;Py_DECREF( r e s u l t ) ;. . .
Ужас
de f sum_th ( l s t ) :r e s u l t = 0f o r i i n l s t : #!
r e s u l t += i . th #!! !111r e t u r n r e s u l t
Это гораздо проще!
Cython
Откомпилировав пример в Cython,я получил следующие результаты:
Python код 14 830C-код, C строка 16 636
С-код, встроенная строка 8 862Cython код 7 067
Выводы
догнать Python’ом C неполучиться;можно потерять 20% просто так;активно используя Cythonможно неплохо ускоритьпрограмму;и мало потерять ввыразительности.
Спасибо завнимание.
Вопросы?