Pull to refresh

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

Reading time 2 min
Views 75K
Original author: James Robert
Всегда знал, что одно из достоинств 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
Tags:
Hubs:
+48
Comments 41
Comments Comments 41

Articles