Всегда знал, что одно из достоинств 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
Автор: siberianMan