Производительность 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 в конечном итоге.)