python и cython
DESCRIPTION
Удобное ускорения Python'а с помощью Cython. Доклад для devconf-2010.TRANSCRIPT
• динамический язык;• байт код без компиляции;• GC на счетчике ссылок;• Global Interpretor Lock;• если что-то работает медленнееpython’а, это не значит чтоpython быстрый.
• динамический язык;• байт код без компиляции;• GC на счетчике ссылок;• Global Interpretor Lock;• если что-то работает медленнееpython’а, это не значит чтоpython быстрый.
Еще про ref-counting
• процессы — плохая заменатредам;
• не работает Copy–on–Write;• мы не можем легкораспараллелить программу,когда у нас много данных.
...d = d i c t ( ( i , ’ the ␣ s t r i n g ␣wi th ␣%d ’ % i )
for i in x range (500∗1000))t ime . s l e e p (10)s y s . s t d e r r . w r i t e ( ’ about ␣ to ␣ f o r k . . . \ n ’ )os . f o r k ( )
for i , k in d . i t e r i t e m s ( ) :pass
t ime . s l e e p (10)
Pure Clong sum_th(const struct with_th_t *all,
size_t size) {long result = 0;size_t i;for (i = 0; i < size; ++i) {
result += all[i].th;}return result;
}
Модуль на Cwhile ((item = PyIter_Next(iterator)) != NULL) {
PyObject *field = PyObject_GetAttr(item, interned_th);
if (field == NULL) {Py_DECREF(result);Py_DECREF(item);return NULL;
}PyObject *nw = PyNumber_Add( result, field);Py_DECREF(result);...
Итого
Python Pure C C moduleстрок 5 7 28
• На реальном коде разница будетменьше!
• Но разница будет.
boxing/unboxing
PyInt_FromSize_t
PyInt_FromLong PyLong_FromSize_t
fill_free_list _PyLong_FromByteArray
_PyLong_New
boxing/unboxingPyInt_AsLong
PyInt_Check tp_as_number
PyInt_AS_LONG PyInt_Check
PyInt_AS_LONG PyLong_Check
PyLong_AsLong
Cython
• страшная смесь Python’а и C;• понимает подмножество Python;• генерирует C-код;• def/cdef/cpdef.• lxml, SciPy, ...
• читайте FAQ;• используйте cdef;• не *q = 0, а q[0] = 0;• не путайте float и double;• cdef для переменных, которыеучаствуют в циклах;
• cython -a.
shlex.split• Ускорим простую программув XX раз.
• За 15 минут.• Сейчас это займет куда меньшевремени.
Задача127.0.0.1 "it’s query" "client id" 12.0 ...
• str.split не сработает: некоторыеполя окружены кавычками.
• 25 миллионов строк в день.• 680 миллионов строк в месяц.
Что имеемsm = 0.0cnt = 0for line in sys.stdin:
split = shlex.split(line)try:
sm += float(split[12])cnt += 1
except ValueError:pass
Что имеемИспользуем стандартный shlex.split и файл в 900строк.
SUMMARYreal time: 0.870 [ 0.875 +-0.004] 0.878sys time: 0.004 [ 0.007 +-0.002] 0.008user time: 0.844 [ 0.860 +-0.014] 0.868
≈ 1 мс на строку.≈ 7 часов на дневной лог
Что имеем
Своя, очень простая реализация. Тот же файл.
SUMMARYreal time: 0.603 [ 0.614 +-0.009] 0.621sys time: 0.000 [ 0.001 +-0.002] 0.004user time: 0.596 [ 0.601 +-0.006] 0.608
≈ 0.7 мс на строку.≈ 5 часов на дневной лог
Что имеем
Та же реализация, но откомпилированная вcython. Тот же файл.
SUMMARYreal time: 0.650 [ 0.664 +-0.013] 0.675sys time: 0.000 [ 0.000 +-0.000] 0.000user time: 0.640 [ 0.655 +-0.014] 0.668
Time table
время0.603 исходный код0.532 объявить типы0.151 cdef class0.029 char *
≈ 14 минут на дневной лог файл.
Проблема boxing/unboxing
1 Найдем функцию, что занимает>50% времени.
2 Перепишем её на C.3 ???
4 Нет профита.
Проблема boxing/unboxing
1 Найдем функцию, что занимает>50% времени.
2 Перепишем её на C.3 ???4 Нет профита.
Профилирование
Подход ускорения черезпрофилирование порочен:• не надо думать, что остальнаяпрограмма ускорится;
• что делать, если нет очевидногомедленного места?
Кроме этого
• на каждый вызов функции надопреобразовывать данные;
• в вырожденных случаях, этобудет дольше самой функции;
• мы вынуждены переписыватьвсе места, где мы вызываемнашу функцию.
Решение
• частичная оптимизация;• cpdef;• перетаскивание функционала вcython, а потом в C;
• это не панацея.
Копирование vs proxy
proxy:• каждый раз преобразовывать;• но есть кеш объектов;• код на C работает быстро;• это тот код, который насинтересует.
Копирование vs proxy
Копирование:• идеально для неизменяемыхобъектов;
• но требует много памяти;• fork...
GIL
• он нужен, пока мы работаем сpython данными;
• в критичных местах, мы неработаем с python данными;
• мы можем его отпустить.
proxy: запрет создания изpython
cdef class _Q:def __cinit__(self, ....):
....
def __init__(self, ....):raise TypeError, ""
cpdef _Q Q(....):cdef _Q q = _Q.__new__(_Q)return q
Кеширование объектов
• попробуйте id;• попробуйте перенумеровать взависимости от частоты;
• убедитесь, что у васнеизменяемые типы;
• треды...
Кеширование проксиобъектов
Python container a[0] a[1] ... a[n]
C struct ... void *extra
container struct Python proxy
Кеширование проксиобъектов
Python container a[0] a[1] ... a[n]
C struct ... void *extra__getitem__
Python proxy container struct next Python proxy ...
Кеширование проксиобъектов
cdef _Point point_wrap(point_t *x):cdef _Point resultif x.extra != NULL:
return <_Point>x.extraresult = _Point.__new__(_Point)result.inner = xif is_common_point(x):
print ’cache’x.extra = <void *>result
return result