Рассмотрим следующие четыре функции (python
, numba
, cython
и smart
), которые вычисляют одинаковые ответы при одинаковых целочисленных входных данных.
def python(n):
total = 0
for m in range(1,n+1):
total += m
return total
from numba import jit
numba = jit(python)
cpdef int cython(int n):
cdef int total = 0
cdef int m
for m in range(1, n+1):
total += m
return total
def smart(n):
return n * (n + 1) // 2
Рассчитав время их казни, я был несколько удивлен, обнаружив, что
- Время выполнения
numba
не зависит отn
(в то время какcython
линейно зависит отn
) numba
медленнее, чемsmart
Сразу возникает два вопроса:
- Почему Numba, а не Cython, может превратить его в алгоритм с постоянным временем?
- Учитывая, что Numba удалось превратить его в алгоритм с постоянным временем, почему он медленнее, чем чистая функция постоянного времени Python
smart
?
Поскольку я не специалист по ассемблеру, просмотр сгенерированного кода на самом деле не дает мне большой подсказки, кроме того, что промежуточный код LLVM, сгенерированный Numba, все еще появляется (хотя я мог неправильно понять), чтобы содержать цикл ... и Я безнадежно теряюсь в x64, который в конечном итоге создается из этого. (Если кто-то не спросит, я не буду публиковать сгенерированные коды, так как они довольно длинные.)
Я запускаю это на x64 Linux в ноутбуке Jupyter, поэтому я подозреваю, что Cython использует GCC 4.4.7, который использовался для компиляции Python; и llvmlite 0.20.0, что подразумевает LLVM 4.0.x.
Редактировать:
я тоже засекал
smart_numba = jit(smart)
а также
cpdef int smart_cython(int n):
return n * (n + 1) // 2
smart_numba
и numba
дают одинаковое время, которое на 25% медленнее, чем smart
(чистый Python), и на 175% медленнее, чем smart_cython
.
Означает ли это, что Cython очень хорошо справляется с преодолением границы между Python и низкоуровневым, а Numba справляется с этой задачей плохо? Или есть что-то еще?