Python и его тормоза
Можно ли заставить питон работать быстро


  Александр Шигин, shigin@rambler-co.ru


             Rambler, 2010
О чем я буду говорить


     краткий рассказ о времени;
     простые примеры;
     быстрые простые примеры;
     глупые примеры;
     и еще глупости.
Время

 Первый пример    real time.
 $ time sleep 5

 real    0m5.006s
 user    0m0.004s
 sys 0m0.008s
Время

 Второй пример   user time.
 $ time openssl rand 
 > -out /dev/null 9000000

 real    0m0.421s
 user    0m0.388s
 sys 0m0.004s
Время
 Третий пример   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
Время
 Для процесса есть три времени:
 real, CPU, system.
     обычно нас интересует real time;
     но если мы запускаем много
     параллельных задач мы можем
     упереться в CPU (user) time;
     системное время в основном
     зависит от количества
     системных вызовов.
Чуть-чуть чисел

     вызов функции по указателю (нс)
           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]
Чуть-чуть числа про объекты


     создание чистого python объекта
     210 нс;
     создание python объекта с
     пустым __slots__ 120 нс;
     создание embedded python
     объекта 150 нс.
Создание объектов

     создание кортежа из двух
     элементов 31 нс;
     создание класса с двумя полями
     690 нс;
     распаковка кортежа 130 нс;
     доступ к полю кортежа 105 нс;
     доступ к полю объекта 110 нс.
Маленький пример

 Как лучше хранить неизменяемые
 пары чисел? Учебный пример
 нахождение центра масс.
    кортеж из трех чисел: x, y,
    масса;
    класс с тремя полями;
    словарь.
Маленький пример: класс


  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)]
Маленький пример: класс


  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 нс
Маленький пример: объекты



    новый класс ≈ 4100 нс;
    в 2.5 новый класс ≈ 4750 нс.
    __slots__ 2.6 ≈ 4050 нс, 2.5
    4500 нс.
Маленький пример: кортеж


  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 нс
Маленький пример: словарь


  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 нс
Маленький пример

 Итог: используйте кортежи.
           Меряем      2.5    2.6
      старый класс    4050   3900
       новый класс    4750   4130
        со слотами    4490   4050
           кортежи    2250   2210
            словарь   2700   2630
Глупый пример


  Смешные числа Фибоначи. Как
  лучше хранить текущую пару
  чисел?

  В этом примере мы будем изменять
  пару.
Глупый пример: класс


  Первый вариант.
  class F :
      def __init__ ( s e l f , x , y ) :
            self .x = x
            self .y = y
Глупый пример: класс

 Проверяем:
 x = F ( 1 , 1)
 t = x.y
 x.y = x.x + x.y
 x.x = t
 # так 4 раза
 Результат: 2830 нс
Глупый пример: классы



     x.x, x.y = x.y, x.x + x.y
     немного медленнее;
     надо повторяться про новые
     классы?
Глупый пример: кортеж


 Проверяем:
 x = 1, 1
 x = 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( 1 4 )
      x (15)       # −> 29
Первый пример           старые классы

  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 нс
Первый пример              новый класс

  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 нс
"Старые" классы


  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 ;
"Новые" классы


  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 ;
Первый пример               странный результат

  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 нс
Первый пример            замыкание

  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 нс
Итераторы/генераторы

      просчитать генератором до
      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 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]
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 ]
passwd: imap




  def f u n c ( l i n e ) :
      x = line . split ( ’ : ’)
      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 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 ) ;
  }
string interning: итог

  В вырожденных случаях C код
  будет медленнее чем Python код.

                   python код 14 830
              C-код, 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

  Все видели картинку про два CPU
  bound треда?




  Картинка нагло украдена из статьи The Python GIL
  Visualized http://www.dabeaz.com/
Global Interpretor Lock


     Во время работы встроенных
     функций можно разблокировать
     GIL, если мы не используем
     Python API.
     Хорошие примеры: py-lxml.
Python API


     Сложно.
     Запутанно.
     Считать ссылки самому:
     Py_DECREF, Py_INCREF

  Брррр...
Ужас
 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 ) ;
          ...
Ужас


 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

 Это гораздо проще!
Cython

  Откомпилировав пример в Cython,
  я получил следующие результаты:
                 Python код 14 830
             C-код, C строка 16 636
   С-код, встроенная строка 8 862
              Cython код 7 067
Выводы

    догнать Python’ом C не
    получиться;
    можно потерять 20% просто так;
    активно используя Cython
    можно неплохо ускорить
    программу;
    и мало потерять в
    выразительности.
Спасибо за
внимание.
Вопросы?

Python и его тормоза

  • 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 hi 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 ef 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 можно неплохо ускорить программу; и мало потерять в выразительности.
  • 52.
  • 53.