Я суммирую каждый элемент в массиве 1D, используя либо Cython, либо NumPy. При суммировании целых чисел Cython на 20% быстрее. При суммировании поплавков Cython ~ 2.5x медленнее. Ниже приведены две простые функции.
#cython: boundscheck=False
#cython: wraparound=False
def sum_int(ndarray[np.int64_t] a):
cdef:
Py_ssize_t i, n = len(a)
np.int64_t total = 0
for i in range(n):
total += a[i]
return total
def sum_float(ndarray[np.float64_t] a):
cdef:
Py_ssize_t i, n = len(a)
np.float64_t total = 0
for i in range(n):
total += a[i]
return total
Задержки
Создайте два массива по 1 миллиону элементов:
a_int = np.random.randint(0, 100, 10**6)
a_float = np.random.rand(10**6)
%timeit sum_int(a_int)
394 µs ± 30 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit a_int.sum()
490 µs ± 34.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit sum_float(a_float)
982 µs ± 10.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit a_float.sum()
383 µs ± 4.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Дополнительные пункты
- NumPy превосходит (с большим запасом) с поплавками и даже превосходит свою собственную целую сумму.
- Разница в производительности для
sum_float
это то же самое сboundscheck
иwraparound
директивы отсутствуют. Зачем? - Преобразование целочисленного массива numpy в
sum_int
в C-указатель (np.int64_t *arr = <np.int64_t *> a.data
) повышает производительность на 25%. Делать это для поплавков ничего не делает
Основной вопрос
Как я могу получить такую же производительность в Cython с поплавками, которые я делаю с целыми числами?
EDIT - только подсчет медленный?!?
Я написал еще более простую функцию, которая просто подсчитывает количество итераций. Первый хранит счет как int, второй - как двойной.
def count_int():
cdef:
Py_ssize_t i, n = 1000000
int ct=0
for i in range(n):
ct += 1
return ct
def count_double():
cdef:
Py_ssize_t i, n = 1000000
double ct=0
for i in range(n):
ct += 1
return ct
Сроки подсчета
Я запускал их только один раз (боюсь кеширования). Не знаю, выполняется ли цикл для целого числа, но count_double
имеет ту же производительность, что и sum_float
сверху. Это безумие...
%timeit -n 1 -r 1 count_int()
1.1 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%timeit -n 1 -r 1 count_double()
971 µs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)