Производительность cpython vs cython vs numpy
Я выполняю некоторые тесты производительности по варианту генератора простых чисел из http://docs.cython.org/src/tutorial/numpy.html.
Приведенные ниже показатели производительности: kmax = 1000
Реализация Pure Python, работающая в CPython: 0.15s
Реализация Pure Python, работающая в Cython: 0.07s
def primes(kmax):
p = []
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p.append(n)
k = k + 1
n = n + 1
return p
Реализация Pure Python + Numpy, работающая в CPython: 1.25s
import numpy
def primes(kmax):
p = numpy.empty(kmax, dtype=int)
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
n = n + 1
return p
Реализация Cython с использованием int *: 0.003s
from libc.stdlib cimport malloc, free
def primes(int kmax):
cdef int n, k, i
cdef int *p = <int *>malloc(kmax * sizeof(int))
result = []
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
result.append(n)
n = n + 1
free(p)
return result
Вышеприведенное замечательно, но выглядит ужасно, поскольку оно хранит две копии данных... поэтому я попытался переопределить его:
Cython + Numpy: 1.01s
import numpy as np
cimport numpy as np
cimport cython
DTYPE = np.int
ctypedef np.int_t DTYPE_t
@cython.boundscheck(False)
def primes(DTYPE_t kmax):
cdef DTYPE_t n, k, i
cdef np.ndarray p = np.empty(kmax, dtype=DTYPE)
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
n = n + 1
return p
Вопросы:
- Почему массив numpy настолько невероятно медленнее, чем список python, при запуске на CPython?
- Что я сделал неправильно в реализации Cython + Numpy? cython, очевидно, НЕ обрабатывает массив numpy как int [], как и должно быть.
-
как мне сделать массив numpy для int *? Ниже не работает
cdef numpy.nparray a = numpy.zeros(100, dtype=int)
cdef int * p = <int *>a.data
Ответы
Ответ 1
Лучший синтаксис, который я нашел до сих пор:
import numpy
cimport numpy
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def primes(int kmax):
cdef int n, k, i
cdef numpy.ndarray[int] p = numpy.empty(kmax, dtype=numpy.int32)
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p[i] != 0:
i = i + 1
if i == k:
p[k] = n
k = k + 1
n = n + 1
return p
Обратите внимание, где я использовал numpy.int32 вместо int. Все, что находится слева от cdef, является C-типом (таким образом, int = int32 и float = float32), в то время как все, что находится на правой стороне (или вне cdef), является типом python (int = int64 и float = float64 )
Ответ 2
cdef DTYPE_t [:] p_view = p
Используя это вместо p в вычислениях. сократил время выполнения от 580 мс до 2,8 мс для меня. О том же времени выполнения, что и реализация с использованием * int. И это о максимуме, которое вы можете ожидать от этого.
DTYPE = np.int
ctypedef np.int_t DTYPE_t
@cython.boundscheck(False)
def primes(DTYPE_t kmax):
cdef DTYPE_t n, k, i
cdef np.ndarray p = np.empty(kmax, dtype=DTYPE)
cdef DTYPE_t [:] p_view = p
k = 0
n = 2
while k < kmax:
i = 0
while i < k and n % p_view[i] != 0:
i = i + 1
if i == k:
p_view[k] = n
k = k + 1
n = n + 1
return p
Ответ 3
почему массив numpy настолько невероятно медленнее, чем список python, при запуске на CPython?
Потому что вы его не полностью написали. Используйте
cdef np.ndarray[dtype=np.int, ndim=1] p = np.empty(kmax, dtype=DTYPE)
как мне сделать массив numpy для int *?
Используя np.intc
как dtype, а не np.int
(который является C long
). Это
cdef np.ndarray[dtype=int, ndim=1] p = np.empty(kmax, dtype=np.intc)
(Но на самом деле, используйте memoryview, они намного чище, и пользователи Cython хотят избавиться от синтаксиса массива NumPy в конечном итоге.)