Производительность в Python. Легкий путь

от автора

Всегда знал, что одно из достоинств Python — возможность переписать самые тормозные куски кода на Си и увеличить быстродействие программы до недостижимых интерпретируемым языкам высот. Но сам ни разу не пробовал, т.к. считал что это слишком сложно. После прочтения этой статьи больше так не считаю.

Программисты знакомые с ctypes врядли найдут тут что-то интересное, новичков же прошу под кат.

Ctypes — механизм Python для импорта функций из внешних библиотек.
%timeit — magic-функция оболочки IPython, измеряющая время выполнения выражения на Python


Ctypes — это прекрасно! Давайте начнем с небольшого банального примера: суммирование чисел в определенном диапазоне.
Вот реализация этой функции на Python

def sumrange(arg):     return sum(xrange(arg)) 

Отлично! Но что если мы попробуем суммировать действительно большой диапазон чисел, например от 0 до 10**8 (т.е. 100,000,000)

In [2]: %timeit sumrange(10**2) 1000000 loops, best of 3: 1.53 us per loop  In [3]: %timeit sumrange(10**8) 1 loops, best of 3: 9.77 s per loop  In [4]: %timeit sumrange(10**9) 1 loops, best of 3: 97.8 s per loop 

Уже не так весело. Попробуем кое-что другое:

def sumrange2(arg):     x = i = 0     while i < arg:         x += i         i += 1     return x 

Что из этого получится?

In [10]: %timeit sumrange2(10**2) 100000 loops, best of 3: 10.5 us per loop  In [11]: %timeit sumrange2(10**8) 1 loops, best of 3: 18.5 s per loop 

Вот это да… Так еще хуже… В этот раз даже не буду пробовать 10**9.
Так как же нам ускорить выполнение? Только не предлагайте математические оптимизации… мы же в новом мире компьютеров! (в оригинале: don’t suggest math tricks… this is the the new world of computing!)
Да, я знаю, что сложность алгоритма — постоянная величина и не зависит о величины аргумента, n*(n+1)/2. Но статья посвящена не этому.

Как насчет ctypes?

#include <stdio.h>  unsigned long long sumrange(unsigned long long arg) {     unsigned long long i, x;     x = 0;      for (i = 0; i < arg; i++) {         x = x + i;     }     return x; }

Сохраним с именем sumrange.c и скомпилируем (не будем использовать оптимизации для чистоты эксперимента):
$ gcc -shared -Wl,-install_name,sumrange.so -o sumrange.so -fPIC sumrange.c

Импортируем в Python то что получилось:

import ctypes sumrange_ctypes = ctypes.CDLL('./sumrange.so').sumrange sumrange_ctypes.restype = ctypes.c_ulonglong sumrange_ctypes.argtypes = ctypes.c_ulonglong, 

И Оскар получает…

In [15]: %timeit sumrange_ctypes(10**2) 1000000 loops, best of 3: 1.28 us per loop  In [16]: %timeit sumrange_ctypes(10**8) 1 loops, best of 3: 381 ms per loop  In [17]: %timeit sumrange_ctypes(10**9) 1 loops, best of 3: 3.79 s per loop  In [18]: %timeit sumrange_ctypes(10**10) 1 loops, best of 3: 37.8 s per loop 

Итоговая сводка:

10**2 10**8 10**9 10**10
Чистый Python, способ №1 1.53 мкс 9.77 с 97.8 с
Чистый Python, способ №2 10.5 мкс 18.5 с
ctypes 1.28 мкс 381 мс 3.79 с 37.8 с

Адский прирост производительности!

Для Node.js хакеров, есть эквивалент ctypes — FFI (Foreign Function Interface): github.com/rbranson/node-ffi

ссылка на оригинал статьи http://habrahabr.ru/post/157537/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *