Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
Python и Cython
    Оптимизация и стыковка с C



Александр Шигин, shigin@rambler-co.ru
           Rambler, 2010
Python   удобно
Python удобно
  ... но не быстро
•   динамический язык;
•   байт код без компиляции;
•   GC на счетчике ссылок;
•   Global Interpretor Lock;
•   если что-то работает медленнее
    python’а, это не значит что
    python быстрый.
•   динамический язык;
•   байт код без компиляции;
•   GC на счетчике ссылок;
•   Global Interpretor Lock;
•   если что-то работает медленнее
    python’а, это не значит что
    python быстрый.
Еще про ref-counting

•   процессы плохая замена
    тредам;
•   не работает Copy–on–Write;
•   мы не можем легко
    распараллелить программу,
    когда у нас много данных.
...
d = d i c t ( ( i , ’ t h e s t r i n g w i t h %d ’ % i )
                for i in x r a n g e ( 5 0 0 ∗ 1 0 0 0 ) )
time . s l e e p (10)
s y s . s t d e r r . w r i t e ( ’ about t o f o r k . . .  n ’ )
os . f o r k ( )

f or i , k in d . i t e r i t e m s ( ) :
     pass
time . s l e e p (10)
C   не так удобно
C       не так удобно
    ... но достаточно
         быстро
C + Python = ???
C + Python =
   медленнее C
C + Python =
   медленнее C
   а удобно?
Python
def sum_th(lst):
    result = 0
    for i in lst:        #!
        result += i.th   #!!!111
    return result
Pure C
long 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;
}
Модуль на C
while ((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


def sum_th(lst):
    result = 0
    for i in lst:        #!
        result += i.th   #!!!111
    return result
Осторожно,
benchmark!
Итого
      Python Pure C C module
строк   5      7       28

•   На реальном коде разница будет
    меньше!
•   Но разница будет.
???
Долго писать:
 • много кода;

 • сложность отладки.



Потеря скорости:
 • boxing/unboxing.
boxing/unboxing
                  PyInt_FromSize_t




PyInt_FromLong                 PyLong_FromSize_t




  fill_free_list             _PyLong_FromByteArray




                                 _PyLong_New
boxing/unboxing
           PyInt_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.0
cnt = 0
for line in sys.stdin:
    split = shlex.split(line)
    try:
         sm += float(split[12])
         cnt += 1
    except ValueError:
         pass
Что имеем
Используем стандартный shlex.split и файл в 900
строк.

       SUMMARY
real time: 0.870 [ 0.875 +-0.004]        0.878
sys time: 0.004 [ 0.007 +-0.002]         0.008
user time: 0.844 [ 0.860 +-0.014]        0.868

≈ 1 мс на строку.
≈ 7 часов на дневной лог
Что имеем
Своя, очень простая реализация. Тот же файл.

       SUMMARY
real time: 0.603 [ 0.614 +-0.009]       0.621
sys time: 0.000 [ 0.001 +-0.002]        0.004
user time: 0.596 [ 0.601 +-0.006]       0.608

≈ 0.7 мс на строку.
≈ 5 часов на дневной лог
Что имеем

Та же реализация, но откомпилированная в
cython. Тот же файл.

       SUMMARY
real time: 0.650 [ 0.664 +-0.013]      0.675
sys time: 0.000 [ 0.000 +-0.000]       0.000
user time: 0.640 [ 0.655 +-0.014]      0.668
cython -a
Уберем Python API
Уберем Python API
Time table
      время
      0.603   исходный код
      0.532   объявить типы
      0.151   cdef class
      0.029   char *
≈ 14 минут на дневной лог файл.
Cython vs ...

•   boost::python;
•   SWIG;
•   expy;
•   ...
Проблема boxing/unboxing

1   Найдем функцию, что занимает
    >50% времени.
2   Перепишем её на C.
3   ???
Проблема boxing/unboxing

1   Найдем функцию, что занимает
    >50% времени.
2   Перепишем её на C.
3   ???
4   Нет профита.
Профилирование
Подход ускорения через
профилирование порочен:
  • не надо думать, что остальная

    программа ускорится;
  • что делать, если нет очевидного

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

•   частичная оптимизация;
•   cpdef;
•   перетаскивание функционала в
    cython, а потом в C;
•   это не панацея.
Сложные данные

Как работать с данными из C и
Python.
 • proxy/wrapper;

 • копия.
Копирование vs proxy
proxy:
  • каждый раз преобразовывать;

  • но есть кеш объектов;

  • код на C работает быстро;

  • это тот код, который нас

    интересует.
Копирование vs proxy

Копирование:
 • идеально для неизменяемых

   объектов;
 • но требует много памяти;

 • fork...
Еще раз про GIL
GIL это не
 проблема
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 result
    if x.extra != NULL:
        return <_Point>x.extra
    result = _Point.__new__(_Point)
    result.inner = x
    if is_common_point(x):
        print ’cache’
        x.extra = <void *>result
    return result
Мои выводы

•   python это удобно;
•   С это быстро;
•   cython объединяет;
•   все будет хорошо.
Благодарю за
  внимание

  shigin@rambler-co.ru
 friendfeed.com/shigin
Вопросы?

 shigin@rambler-co.ru
friendfeed.com/shigin

More Related Content

Python и Cython

  • 1. Python и Cython Оптимизация и стыковка с C Александр Шигин, shigin@rambler-co.ru Rambler, 2010
  • 2. Python удобно
  • 3. Python удобно ... но не быстро
  • 4. динамический язык; • байт код без компиляции; • GC на счетчике ссылок; • Global Interpretor Lock; • если что-то работает медленнее python’а, это не значит что python быстрый.
  • 5. динамический язык; • байт код без компиляции; • GC на счетчике ссылок; • Global Interpretor Lock; • если что-то работает медленнее python’а, это не значит что python быстрый.
  • 6. Еще про ref-counting • процессы плохая замена тредам; • не работает Copy–on–Write; • мы не можем легко распараллелить программу, когда у нас много данных.
  • 7. ... d = d i c t ( ( i , ’ t h e s t r i n g w i t h %d ’ % i ) for i in x r a n g e ( 5 0 0 ∗ 1 0 0 0 ) ) time . s l e e p (10) s y s . s t d e r r . w r i t e ( ’ about t o f o r k . . . n ’ ) os . f o r k ( ) f or i , k in d . i t e r i t e m s ( ) : pass time . s l e e p (10)
  • 8. C не так удобно
  • 9. C не так удобно ... но достаточно быстро
  • 10. C + Python = ???
  • 11. C + Python = медленнее C
  • 12. C + Python = медленнее C а удобно?
  • 13. Python def sum_th(lst): result = 0 for i in lst: #! result += i.th #!!!111 return result
  • 14. Pure C long 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; }
  • 15. Модуль на C while ((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); ...
  • 16. Python def sum_th(lst): result = 0 for i in lst: #! result += i.th #!!!111 return result
  • 18. Итого Python Pure C C module строк 5 7 28 • На реальном коде разница будет меньше! • Но разница будет.
  • 19. ??? Долго писать: • много кода; • сложность отладки. Потеря скорости: • boxing/unboxing.
  • 20. boxing/unboxing PyInt_FromSize_t PyInt_FromLong PyLong_FromSize_t fill_free_list _PyLong_FromByteArray _PyLong_New
  • 21. boxing/unboxing PyInt_AsLong PyInt_Check tp_as_number PyInt_AS_LONG PyInt_Check PyInt_AS_LONG PyLong_Check PyLong_AsLong
  • 22. Cython • страшная смесь Python’а и C; • понимает подмножество Python; • генерирует C-код; • def/cdef/cpdef. • lxml, SciPy, ...
  • 23. читайте FAQ; • используйте cdef; • не *q = 0, а q[0] = 0; • не путайте float и double; • cdef для переменных, которые участвуют в циклах; • cython -a.
  • 24. shlex.split • Ускорим простую программу в XX раз. • За 15 минут. • Сейчас это займет куда меньше времени.
  • 25. Задача 127.0.0.1 "it’s query" "client id" 12.0 ... • str.split не сработает: некоторые поля окружены кавычками. • 25 миллионов строк в день. • 680 миллионов строк в месяц.
  • 26. Что имеем sm = 0.0 cnt = 0 for line in sys.stdin: split = shlex.split(line) try: sm += float(split[12]) cnt += 1 except ValueError: pass
  • 27. Что имеем Используем стандартный shlex.split и файл в 900 строк. SUMMARY real time: 0.870 [ 0.875 +-0.004] 0.878 sys time: 0.004 [ 0.007 +-0.002] 0.008 user time: 0.844 [ 0.860 +-0.014] 0.868 ≈ 1 мс на строку. ≈ 7 часов на дневной лог
  • 28. Что имеем Своя, очень простая реализация. Тот же файл. SUMMARY real time: 0.603 [ 0.614 +-0.009] 0.621 sys time: 0.000 [ 0.001 +-0.002] 0.004 user time: 0.596 [ 0.601 +-0.006] 0.608 ≈ 0.7 мс на строку. ≈ 5 часов на дневной лог
  • 29. Что имеем Та же реализация, но откомпилированная в cython. Тот же файл. SUMMARY real time: 0.650 [ 0.664 +-0.013] 0.675 sys time: 0.000 [ 0.000 +-0.000] 0.000 user time: 0.640 [ 0.655 +-0.014] 0.668
  • 33. Time table время 0.603 исходный код 0.532 объявить типы 0.151 cdef class 0.029 char * ≈ 14 минут на дневной лог файл.
  • 34. Cython vs ... • boost::python; • SWIG; • expy; • ...
  • 35. Проблема boxing/unboxing 1 Найдем функцию, что занимает >50% времени. 2 Перепишем её на C. 3 ???
  • 36. Проблема boxing/unboxing 1 Найдем функцию, что занимает >50% времени. 2 Перепишем её на C. 3 ??? 4 Нет профита.
  • 37. Профилирование Подход ускорения через профилирование порочен: • не надо думать, что остальная программа ускорится; • что делать, если нет очевидного медленного места?
  • 38. Кроме этого • на каждый вызов функции надо преобразовывать данные; • в вырожденных случаях, это будет дольше самой функции; • мы вынуждены переписывать все места, где мы вызываем нашу функцию.
  • 39. Решение • частичная оптимизация; • cpdef; • перетаскивание функционала в cython, а потом в C; • это не панацея.
  • 40. Сложные данные Как работать с данными из C и Python. • proxy/wrapper; • копия.
  • 41. Копирование vs proxy proxy: • каждый раз преобразовывать; • но есть кеш объектов; • код на C работает быстро; • это тот код, который нас интересует.
  • 42. Копирование vs proxy Копирование: • идеально для неизменяемых объектов; • но требует много памяти; • fork...
  • 44. GIL это не проблема
  • 45. GIL • он нужен, пока мы работаем с python данными; • в критичных местах, мы не работаем с python данными; • мы можем его отпустить.
  • 46. proxy: запрет создания из python cdef class _Q: def __cinit__(self, ....): .... def __init__(self, ....): raise TypeError, "" cpdef _Q Q(....): cdef _Q q = _Q.__new__(_Q) return q
  • 47. Кеширование объектов • попробуйте id; • попробуйте перенумеровать в зависимости от частоты; • убедитесь, что у вас неизменяемые типы; • треды...
  • 48. Кеширование прокси объектов Python container a[0] a[1] ... a[n] C struct ... void *extra container struct Python proxy
  • 49. Кеширование прокси объектов Python container a[0] a[1] ... a[n] C struct ... void *extra __getitem__ Python proxy container struct next Python proxy ...
  • 50. Кеширование прокси объектов cdef _Point point_wrap(point_t *x): cdef _Point result if x.extra != NULL: return <_Point>x.extra result = _Point.__new__(_Point) result.inner = x if is_common_point(x): print ’cache’ x.extra = <void *>result return result
  • 51. Мои выводы • python это удобно; • С это быстро; • cython объединяет; • все будет хорошо.
  • 52. Благодарю за внимание shigin@rambler-co.ru friendfeed.com/shigin