1. Python и его тормоза
Можно ли заставить питон работать быстро
Александр Шигин, shigin@rambler-co.ru
Rambler, 2010
2. О чем я буду говорить
краткий рассказ о времени;
простые примеры;
быстрые простые примеры;
глупые примеры;
и еще глупости.
3. Время
Первый пример real time.
$ time sleep 5
real 0m5.006s
user 0m0.004s
sys 0m0.008s
4. Время
Второй пример user time.
$ time openssl rand
> -out /dev/null 9000000
real 0m0.421s
user 0m0.388s
sys 0m0.004s
5. Время
Третий пример system time.
$ time LANG=C dd if=/dev/zero
> of=/dev/null count=900000 bs=1
900000+0 records in
900000+0 records out
900000 bytes (900 kB) copied, 0.413783
real 0m0.417s
user 0m0.096s
sys 0m0.312s
6. Время
Для процесса есть три времени:
real, CPU, system.
обычно нас интересует real time;
но если мы запускаем много
параллельных задач мы можем
упереться в CPU (user) time;
системное время в основном
зависит от количества
системных вызовов.
7. Чуть-чуть чисел
вызов функции по указателю (нс)
C Python bi Python pure
4 140 270 [2.5]
130 260 [2.6]
сумма массива из 400 целых чисел
C Python bi Python pure
590 16 550 49 300 [2.5]
5 700 45 150 [2.6]
8. Чуть-чуть числа про объекты
создание чистого python объекта
210 нс;
создание python объекта с
пустым __slots__ 120 нс;
создание embedded python
объекта 150 нс.
9. Создание объектов
создание кортежа из двух
элементов 31 нс;
создание класса с двумя полями
690 нс;
распаковка кортежа 130 нс;
доступ к полю кортежа 105 нс;
доступ к полю объекта 110 нс.
10. Маленький пример
Как лучше хранить неизменяемые
пары чисел? Учебный пример
нахождение центра масс.
кортеж из трех чисел: x, y,
масса;
класс с тремя полями;
словарь.
11. Маленький пример: класс
class P o s i t i o n :
def __init__ ( s e l f , x , y , mass ) :
self .x = x
self .y = y
s e l f . mass = mass
x = [ P o s i ti o n (0 , 0 , 10) ,
P o si t i on (1 , 1 , 13) ,
Position (1 , 2 , 5)]
12. Маленький пример: класс
def g e t _ c e n t e r ( l s t ) :
rx , ry , mass = 0 . 0 , 0 . 0 , 0 . 0
for i t e m in l s t :
r x += i t e m . x ∗ i t e m . mass
r y += i t e m . y ∗ i t e m . mass
mass += i t e m . mass
return P o s i t i o n ( r x / mass , r y / mass ,
mass )
Результат: 3900 нс
13. Маленький пример: объекты
новый класс ≈ 4100 нс;
в 2.5 новый класс ≈ 4750 нс.
__slots__ 2.6 ≈ 4050 нс, 2.5
4500 нс.
14. Маленький пример: кортеж
def g e t _ c e n t e r ( l s t ) :
rx , ry , rm = 0 . 0 , 0 . 0 , 0 . 0
for x , y , mass in l s t :
r x += x ∗ mass
r y += y ∗ mass
rm += mass
return ( r x /rm , r y /rm , rm )
Результат: 2200 нс
15. Маленький пример: словарь
def g e t _ c e n t e r ( l s t ) :
rx , ry , rm = 0 . 0 , 0 . 0 , 0 . 0
for i t e m in l s t :
r x += i t e m [ ’ x ’ ] ∗ i t e m [ ’ mass ’ ]
r y += i t e m [ ’ y ’ ] ∗ i t e m [ ’ mass ’ ]
rm += i t e m [ ’ mass ’ ]
return ( r x /rm , r y /rm , rm )
Результат: 2600 нс
16. Маленький пример
Итог: используйте кортежи.
Меряем 2.5 2.6
старый класс 4050 3900
новый класс 4750 4130
со слотами 4490 4050
кортежи 2250 2210
словарь 2700 2630
17. Глупый пример
Смешные числа Фибоначи. Как
лучше хранить текущую пару
чисел?
В этом примере мы будем изменять
пару.
18. Глупый пример: класс
Первый вариант.
class F :
def __init__ ( s e l f , x , y ) :
self .x = x
self .y = y
19. Глупый пример: класс
Проверяем:
x = F ( 1 , 1)
t = x.y
x.y = x.x + x.y
x.x = t
# так 4 раза
Результат: 2830 нс
20. Глупый пример: классы
x.x, x.y = x.y, x.x + x.y
немного медленнее;
надо повторяться про новые
классы?
21. Глупый пример: кортеж
Проверяем:
x = 1, 1
x = x [1] , x [0] + x [1]
# так 4 раза
Результат: 1410 нс
22. Глупый пример: словарь
Проверяем:
x = d i c t ( x =1, y=1)
t = x [ ’y ’ ]
x[ ’y ’ ] = x[ ’x ’ ] + x[ ’y ’ ]
x[ ’x ’ ] = t
# так 4 раза
Результат: 2500 нс
23. Глупый пример
Итог: даже в этом случае
используйте кортежи.
Меряем 2.5 2.6
старый класс 2910 2835
новый класс 4630 3468
со слотами 4065 3212
кортежи 1550 1410
словарь 2810 2550
24. Замыкания
вызов замыкания 145 нс;
возврат значения из замыкания
166 нс;
создание замыкания 380 нс;
25. Первый пример
Создадим класс, экземпляры
которого будут возвращать свой
аргумент, увеличенный на некое
число, которое мы передадим при
создании класса.
x = X( 1 4 )
x (15) # −> 29
26. Первый пример старые классы
class X:
def __init__ ( s e l f , x ) :
self .x = x
def __call__ ( s e l f , x ) :
return s e l f . x + x
x = X(1); s = 0
# проверяем
s += x ( 1 2 )
Результат: 650 нс
27. Первый пример новый класс
c l a s s X( o b j e c t ) :
def __init__ ( s e l f , x ) :
self .x = x
def __call__ ( s e l f , x ) :
return s e l f . x + x
x = X(1); s = 0
# проверяем
s += x ( 1 2 )
Результат: 650 → 404 нс
28. "Старые" классы
typedef struct {
PyObject_HEAD
PyObject ∗ cl_bases ;
PyObject ∗cl_dict ;
PyObject ∗cl_name ;
PyObject ∗ cl_get / set / d e l a t t r ;
} PyClassObject ;
29. "Новые" классы
typedef struct _ t y p e o b j e c t {
PyObject_VAR_HEAD
/∗ s k i p f i e l d s ∗/
h a s h f u n c tp_hash ;
ternaryfunc tp_call ;
/∗ s k i p f i e l d s ∗/
} PyTypeObject ;
30. Первый пример странный результат
class X:
def __init__ ( s e l f , x ) :
self .x = x
def c a l l ( s e l f , x ) :
return s e l f . x + x
x = X(1); s = 0
# проверяем
s += x . c a l l ( 1 2 )
Результат: 650 → 404 → 380 нс
31. Первый пример замыкание
def X( x ) :
def i n n e r ( y ) :
return x+y
return i n n e r
x = X(1); s = 0
# проверяем
s += x ( 1 2 )
Результат: 650 → 404 → 380 → 228 нс
32. Итераторы/генераторы
просчитать генератором до
100 31 700 нс;
просчитать итератором до
100 82 200 нс;
itertools.count 7 400 нс;
xrange(999) 7 500 нс.
Проверка через list(islice(iterable, 100)).
33. Чуть-чуть реальный пример
Выделить тройки (uid, login,
домашняя директория) из
/etc/passwd.
Это достаточно часть первого этапа
обработки больших файлов.
34. passwd: генератор
def f u n c ( i a b l e ) :
for l i n e in i a b l e :
x = line . split ( ’ : ’)
yield int (x [2]) , x [0] , x [5]
35. passwd: итератор
class func :
def __init__ ( s e l f , i a b l e ) :
self . iable = iable
def __iter__ ( s e l f ) : return s e l f
def n e x t ( s e l f ) :
l i n e = s e l f . i a b l e . next ()
x = line . split ( ’ : ’)
return i n t ( x [ 2 ] ) , x [ 0 ] , x [ 5 ]
36. passwd: imap
def f u n c ( l i n e ) :
x = line . split ( ’ : ’)
return i n t ( x [ 2 ] ) , x [ 0 ] , x [ 5 ]
37. passwd
Вывод: генераторы это не только
удобно, но и быстро.
2.5 2.6
генератор 125 626 148 311
итератор 181 100 210 167
imap 146 084 167 703
38. Некоторые общие вещи
Некоторые способы ускорить
встроенный код:
interning;
proxy-object;
object cache.
39. string interning
Создание объекта требует
дополнительных действий.
Если мы используем одну и ту
же строку постоянно, гораздо
быстрее создать один раз python
строку, а потом передавать ее в
python API.
Эта техника подходит не только
для строк.
40. string interning: пример
while ( ( i t e m = P y I t e r _ N e x t ( i t e r a t o r ) )
!= NULL) {
PyObject ∗ f i e l d =
PyObject_GetAttrString (
item , " t h " ) ;
// PyObject ∗ f i e l d =
// PyObject_GetAttr (
// item , i n t e r n e d _ t h ) ;
...
Py_DECREF( i t e m ) ;
}
41. string interning: итог
В вырожденных случаях C код
будет медленнее чем Python код.
python код 14 830
C-код, C строка 16 636
С-код, встроенная строка 8 862
42. proxy object
Если у вас есть развесистая
структура нативных
объектов, не надо для них всех
создавать python объекты.
Создание объектов по
требованию экономит не только
время создания родительского
объекта, но и память.
43. object cache
Если объекты неизменяемые и
одни объекты встречаются чаще
чем другие, можно возвращать
заранее созданные объекты.
Хорошие примеры: целые числа,
короткие строки, узел дерева без
детей, единичные массивы.
44. object cache
создание 50 тысяч int(5) 450
мкс (9 нс);
создание 50 тысяч int(5000)
760 мкс (15 нс);
кеш объектов в данном случае
дал прирост в 40%.
45. Global Interpretor Lock
Все видели картинку про два CPU
bound треда?
Картинка нагло украдена из статьи The Python GIL
Visualized http://www.dabeaz.com/
46. Global Interpretor Lock
Во время работы встроенных
функций можно разблокировать
GIL, если мы не используем
Python API.
Хорошие примеры: py-lxml.
47. Python API
Сложно.
Запутанно.
Считать ссылки самому:
Py_DECREF, Py_INCREF
Брррр...
48. Ужас
w h i l e (( item = PyIter_Next ( i t e r a t o r ))
!= NULL) {
PyObject ∗ f i e l d = PyObject_GetAttr (
item , i n t e r n e d _ t h ) ;
i f ( f i e l d == NULL) {
Py_DECREF( r e s u l t ) ;
Py_DECREF( i t e m ) ;
r e t u r n NULL ;
}
PyObject ∗nw = PyNumber_Add (
result , field );
Py_DECREF( r e s u l t ) ;
...
49. Ужас
d e f sum_th ( l s t ) :
result = 0
for i in l s t : #!
r e s u l t += i . t h #!!!111
return result
Это гораздо проще!
50. Cython
Откомпилировав пример в Cython,
я получил следующие результаты:
Python код 14 830
C-код, C строка 16 636
С-код, встроенная строка 8 862
Cython код 7 067
51. Выводы
догнать Python’ом C не
получиться;
можно потерять 20% просто так;
активно используя Cython
можно неплохо ускорить
программу;
и мало потерять в
выразительности.