diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0e4c992..6e26198 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,4 +23,13 @@ jobs: python -m pip install -r requirements.txt pip install . - name: Run tests - run: pytest -v --pyargs cyarray + run: pytest -v --pyargs cyarray --benchmark-save benchmark-stats --benchmark-json benchmark-full.json --benchmark-histogram + + - name: 'Upload Perf Data' + uses: actions/upload-artifact@v4 + with: + name: perf-bench-result-${{ matrix.python-version }}-${{ matrix.os }} + path: | + benchmark_*.svg + .benchmarks/* + benchmark-full.json diff --git a/cyarray/carray.pxd b/cyarray/carray.pxd index bde925f..7603f50 100644 --- a/cyarray/carray.pxd +++ b/cyarray/carray.pxd @@ -12,10 +12,10 @@ Declaration File. # numpy import cimport numpy as np -cdef long aligned(long n, int item_size) nogil -cdef void* aligned_malloc(size_t bytes) nogil -cdef void* aligned_realloc(void* existing, size_t bytes, size_t old_size) nogil -cdef void aligned_free(void* p) nogil +cdef long aligned(long n, int item_size) noexcept nogil +cdef void* aligned_malloc(size_t bytes) noexcept nogil +cdef void* aligned_realloc(void* existing, size_t bytes, size_t old_size) noexcept nogil +cdef void aligned_free(void* p) noexcept nogil # forward declaration cdef class BaseArray @@ -30,11 +30,11 @@ cdef class BaseArray: cdef public long length, alloc cdef np.ndarray _npy_array - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_reserve(self, long size) nogil - cdef void c_reset(self) nogil - cdef void c_resize(self, long size) nogil - cdef void c_squeeze(self) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_reserve(self, long size) noexcept nogil + cdef void c_reset(self) noexcept nogil + cdef void c_resize(self, long size) noexcept nogil + cdef void c_squeeze(self) noexcept nogil cpdef reserve(self, long size) cpdef resize(self, long size) @@ -62,9 +62,9 @@ cdef class IntArray(BaseArray): cdef IntArray _parent cdef _setup_npy_array(self) - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_append(self, int value) nogil - cdef void c_set_view(self, int *array, long length) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_append(self, int value) noexcept nogil + cdef void c_set_view(self, int *array, long length) noexcept nogil cdef int* get_data_ptr(self) cpdef int get(self, long idx) @@ -92,9 +92,9 @@ cdef class UIntArray(BaseArray): cdef UIntArray _parent cdef _setup_npy_array(self) - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_append(self, unsigned int value) nogil - cdef void c_set_view(self, unsigned int *array, long length) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_append(self, unsigned int value) noexcept nogil + cdef void c_set_view(self, unsigned int *array, long length) noexcept nogil cdef unsigned int* get_data_ptr(self) cpdef unsigned int get(self, long idx) @@ -122,9 +122,9 @@ cdef class LongArray(BaseArray): cdef LongArray _parent cdef _setup_npy_array(self) - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_append(self, long value) nogil - cdef void c_set_view(self, long *array, long length) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_append(self, long value) noexcept nogil + cdef void c_set_view(self, long *array, long length) noexcept nogil cdef long* get_data_ptr(self) cpdef long get(self, long idx) @@ -152,9 +152,9 @@ cdef class FloatArray(BaseArray): cdef FloatArray _parent cdef _setup_npy_array(self) - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_append(self, float value) nogil - cdef void c_set_view(self, float *array, long length) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_append(self, float value) noexcept nogil + cdef void c_set_view(self, float *array, long length) noexcept nogil cdef float* get_data_ptr(self) cpdef float get(self, long idx) @@ -182,9 +182,9 @@ cdef class DoubleArray(BaseArray): cdef DoubleArray _parent cdef _setup_npy_array(self) - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_append(self, double value) nogil - cdef void c_set_view(self, double *array, long length) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_append(self, double value) noexcept nogil + cdef void c_set_view(self, double *array, long length) noexcept nogil cdef double* get_data_ptr(self) cpdef double get(self, long idx) diff --git a/cyarray/carray.pxd.mako b/cyarray/carray.pxd.mako index 72f874c..4117946 100644 --- a/cyarray/carray.pxd.mako +++ b/cyarray/carray.pxd.mako @@ -20,10 +20,10 @@ Declaration File. # numpy import cimport numpy as np -cdef long aligned(long n, int item_size) nogil -cdef void* aligned_malloc(size_t bytes) nogil -cdef void* aligned_realloc(void* existing, size_t bytes, size_t old_size) nogil -cdef void aligned_free(void* p) nogil +cdef long aligned(long n, int item_size) noexcept nogil +cdef void* aligned_malloc(size_t bytes) noexcept nogil +cdef void* aligned_realloc(void* existing, size_t bytes, size_t old_size) noexcept nogil +cdef void aligned_free(void* p) noexcept nogil # forward declaration cdef class BaseArray @@ -38,11 +38,11 @@ cdef class BaseArray: cdef public long length, alloc cdef np.ndarray _npy_array - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_reserve(self, long size) nogil - cdef void c_reset(self) nogil - cdef void c_resize(self, long size) nogil - cdef void c_squeeze(self) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_reserve(self, long size) noexcept nogil + cdef void c_reset(self) noexcept nogil + cdef void c_resize(self, long size) noexcept nogil + cdef void c_squeeze(self) noexcept nogil cpdef reserve(self, long size) cpdef resize(self, long size) @@ -71,9 +71,9 @@ cdef class ${CLASSNAME}(BaseArray): cdef ${CLASSNAME} _parent cdef _setup_npy_array(self) - cdef void c_align_array(self, LongArray new_indices, int stride=*) nogil - cdef void c_append(self, ${ARRAY_TYPE} value) nogil - cdef void c_set_view(self, ${ARRAY_TYPE} *array, long length) nogil + cdef void c_align_array(self, LongArray new_indices, int stride=*) noexcept nogil + cdef void c_append(self, ${ARRAY_TYPE} value) noexcept nogil + cdef void c_set_view(self, ${ARRAY_TYPE} *array, long length) noexcept nogil cdef ${ARRAY_TYPE}* get_data_ptr(self) cpdef ${ARRAY_TYPE} get(self, long idx) diff --git a/cyarray/carray.pyx b/cyarray/carray.pyx index 213ebc8..29cf108 100644 --- a/cyarray/carray.pyx +++ b/cyarray/carray.pyx @@ -29,13 +29,11 @@ The numpy array may however be copied and used in any manner. # For malloc etc. from libc.stdlib cimport * -IF UNAME_SYSNAME == "Windows": - cdef extern from "msstdint.h" nogil: - ctypedef unsigned int uintptr_t -ELSE: - from libc.stdint cimport uintptr_t + +from libc.stdint cimport uintptr_t cimport numpy as np +np.import_array() import numpy as np @@ -63,12 +61,22 @@ cdef extern from "numpy/arrayobject.h": # memcpy cdef extern from "stdlib.h": - void *memcpy(void *dst, void *src, long n) nogil + void *memcpy(void *dst, void *src, long n) noexcept nogil + +# Cython>= 3.0 now disallows replacing the guts of a numpy array. +# Workaround: https://github.com/rainwoodman/pandas/blob/05d3fe2402e4563124e7060837ded7513ab5bca7/pandas/_libs/reduction.pyx#L27 # noqa: E501 +cdef extern from *: + """ + static void PyArray_SET_DATA(PyArrayObject *arr, char * data) { + arr->data = data; + } + """ + void PyArray_SET_DATA(np.ndarray arr, char * data) noexcept nogil # numpy module initialization call _import_array() -cdef inline long aligned(long n, int item_size) nogil: +cdef inline long aligned(long n, int item_size) noexcept nogil: """Align `n` items each having size (in bytes) `item_size` to 64 bytes and return the appropriate number of items that would be aligned to 64 bytes. @@ -88,7 +96,7 @@ cpdef long py_aligned(long n, int item_size): """ return aligned(n, item_size) -cdef void* _aligned_malloc(size_t bytes) nogil: +cdef void* _aligned_malloc(size_t bytes) noexcept nogil: """Allocates block of memory starting on a cache line. Algorithm from: @@ -105,7 +113,7 @@ cdef void* _aligned_malloc(size_t bytes) nogil: return result -cdef void* _aligned_realloc(void *existing, size_t bytes, size_t old_size) nogil: +cdef void* _aligned_realloc(void *existing, size_t bytes, size_t old_size) noexcept nogil: """Allocates block of memory starting on a cache line. """ @@ -118,7 +126,7 @@ cdef void* _aligned_realloc(void *existing, size_t bytes, size_t old_size) nogil return result -cdef void* _deref_base(void* ptr) nogil: +cdef void* _deref_base(void* ptr) noexcept nogil: cdef size_t cache_size = 64 # Recover where block actually starts cdef char* base = (ptr)[-1] @@ -127,13 +135,13 @@ cdef void* _deref_base(void* ptr) nogil: raise MemoryError("Passed pointer is not aligned.") return base -cdef void* aligned_malloc(size_t bytes) nogil: +cdef void* aligned_malloc(size_t bytes) noexcept nogil: return _aligned_malloc(bytes) -cdef void* aligned_realloc(void* p, size_t bytes, size_t old_size) nogil: +cdef void* aligned_realloc(void* p, size_t bytes, size_t old_size) noexcept nogil: return _aligned_realloc(p, bytes, old_size) -cdef void aligned_free(void* p) nogil: +cdef void aligned_free(void* p) noexcept nogil: """Free block allocated by alligned_malloc. """ free(_deref_base(p)) @@ -144,23 +152,23 @@ cdef class BaseArray: """ - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ pass - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: pass - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array self.length = 0 arr.dimensions[0] = self.length - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: pass - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: pass @@ -387,7 +395,7 @@ cdef class IntArray(BaseArray): ) - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ @@ -416,7 +424,7 @@ cdef class IntArray(BaseArray): aligned_free(temp) - cdef void c_append(self, int value) nogil: + cdef void c_append(self, int value) noexcept nogil: cdef long l = self.length cdef PyArrayObject* arr = self._npy_array @@ -428,7 +436,7 @@ cdef class IntArray(BaseArray): # update the numpy arrays length arr.dimensions[0] = self.length - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL if size > self.alloc: @@ -446,14 +454,14 @@ cdef class IntArray(BaseArray): self.alloc = size arr.data = self.data - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: BaseArray.c_reset(self) if self._old_data != NULL: self.data = self._old_data self._old_data = NULL - self._npy_array.data = self.data + PyArray_SET_DATA(self._npy_array, self.data) - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array # reserve memory @@ -463,7 +471,7 @@ cdef class IntArray(BaseArray): self.length = size arr.dimensions[0] = self.length - cdef void c_set_view(self, int *array, long length) nogil: + cdef void c_set_view(self, int *array, long length) noexcept nogil: """Create a view of a given raw data pointer with given length. """ if self._old_data == NULL: @@ -475,7 +483,7 @@ cdef class IntArray(BaseArray): arr.data = self.data arr.dimensions[0] = self.length - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL cdef size_t size = max(self.length, 16) @@ -892,7 +900,7 @@ cdef class UIntArray(BaseArray): ) - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ @@ -921,7 +929,7 @@ cdef class UIntArray(BaseArray): aligned_free(temp) - cdef void c_append(self, unsigned int value) nogil: + cdef void c_append(self, unsigned int value) noexcept nogil: cdef long l = self.length cdef PyArrayObject* arr = self._npy_array @@ -933,7 +941,7 @@ cdef class UIntArray(BaseArray): # update the numpy arrays length arr.dimensions[0] = self.length - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL if size > self.alloc: @@ -951,14 +959,14 @@ cdef class UIntArray(BaseArray): self.alloc = size arr.data = self.data - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: BaseArray.c_reset(self) if self._old_data != NULL: self.data = self._old_data self._old_data = NULL - self._npy_array.data = self.data + PyArray_SET_DATA(self._npy_array, self.data) - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array # reserve memory @@ -968,7 +976,7 @@ cdef class UIntArray(BaseArray): self.length = size arr.dimensions[0] = self.length - cdef void c_set_view(self, unsigned int *array, long length) nogil: + cdef void c_set_view(self, unsigned int *array, long length) noexcept nogil: """Create a view of a given raw data pointer with given length. """ if self._old_data == NULL: @@ -980,7 +988,7 @@ cdef class UIntArray(BaseArray): arr.data = self.data arr.dimensions[0] = self.length - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL cdef size_t size = max(self.length, 16) @@ -1397,7 +1405,7 @@ cdef class LongArray(BaseArray): ) - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ @@ -1426,7 +1434,7 @@ cdef class LongArray(BaseArray): aligned_free(temp) - cdef void c_append(self, long value) nogil: + cdef void c_append(self, long value) noexcept nogil: cdef long l = self.length cdef PyArrayObject* arr = self._npy_array @@ -1438,7 +1446,7 @@ cdef class LongArray(BaseArray): # update the numpy arrays length arr.dimensions[0] = self.length - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL if size > self.alloc: @@ -1456,14 +1464,14 @@ cdef class LongArray(BaseArray): self.alloc = size arr.data = self.data - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: BaseArray.c_reset(self) if self._old_data != NULL: self.data = self._old_data self._old_data = NULL - self._npy_array.data = self.data + PyArray_SET_DATA(self._npy_array, self.data) - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array # reserve memory @@ -1473,7 +1481,7 @@ cdef class LongArray(BaseArray): self.length = size arr.dimensions[0] = self.length - cdef void c_set_view(self, long *array, long length) nogil: + cdef void c_set_view(self, long *array, long length) noexcept nogil: """Create a view of a given raw data pointer with given length. """ if self._old_data == NULL: @@ -1485,7 +1493,7 @@ cdef class LongArray(BaseArray): arr.data = self.data arr.dimensions[0] = self.length - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL cdef size_t size = max(self.length, 16) @@ -1902,7 +1910,7 @@ cdef class FloatArray(BaseArray): ) - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ @@ -1931,7 +1939,7 @@ cdef class FloatArray(BaseArray): aligned_free(temp) - cdef void c_append(self, float value) nogil: + cdef void c_append(self, float value) noexcept nogil: cdef long l = self.length cdef PyArrayObject* arr = self._npy_array @@ -1943,7 +1951,7 @@ cdef class FloatArray(BaseArray): # update the numpy arrays length arr.dimensions[0] = self.length - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL if size > self.alloc: @@ -1961,14 +1969,14 @@ cdef class FloatArray(BaseArray): self.alloc = size arr.data = self.data - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: BaseArray.c_reset(self) if self._old_data != NULL: self.data = self._old_data self._old_data = NULL - self._npy_array.data = self.data + PyArray_SET_DATA(self._npy_array, self.data) - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array # reserve memory @@ -1978,7 +1986,7 @@ cdef class FloatArray(BaseArray): self.length = size arr.dimensions[0] = self.length - cdef void c_set_view(self, float *array, long length) nogil: + cdef void c_set_view(self, float *array, long length) noexcept nogil: """Create a view of a given raw data pointer with given length. """ if self._old_data == NULL: @@ -1990,7 +1998,7 @@ cdef class FloatArray(BaseArray): arr.data = self.data arr.dimensions[0] = self.length - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL cdef size_t size = max(self.length, 16) @@ -2407,7 +2415,7 @@ cdef class DoubleArray(BaseArray): ) - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ @@ -2436,7 +2444,7 @@ cdef class DoubleArray(BaseArray): aligned_free(temp) - cdef void c_append(self, double value) nogil: + cdef void c_append(self, double value) noexcept nogil: cdef long l = self.length cdef PyArrayObject* arr = self._npy_array @@ -2448,7 +2456,7 @@ cdef class DoubleArray(BaseArray): # update the numpy arrays length arr.dimensions[0] = self.length - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL if size > self.alloc: @@ -2466,14 +2474,14 @@ cdef class DoubleArray(BaseArray): self.alloc = size arr.data = self.data - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: BaseArray.c_reset(self) if self._old_data != NULL: self.data = self._old_data self._old_data = NULL - self._npy_array.data = self.data + PyArray_SET_DATA(self._npy_array, self.data) - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array # reserve memory @@ -2483,7 +2491,7 @@ cdef class DoubleArray(BaseArray): self.length = size arr.dimensions[0] = self.length - cdef void c_set_view(self, double *array, long length) nogil: + cdef void c_set_view(self, double *array, long length) noexcept nogil: """Create a view of a given raw data pointer with given length. """ if self._old_data == NULL: @@ -2495,7 +2503,7 @@ cdef class DoubleArray(BaseArray): arr.data = self.data arr.dimensions[0] = self.length - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL cdef size_t size = max(self.length, 16) diff --git a/cyarray/carray.pyx.mako b/cyarray/carray.pyx.mako index 30208be..cf75c31 100644 --- a/cyarray/carray.pyx.mako +++ b/cyarray/carray.pyx.mako @@ -1,4 +1,5 @@ <% +import platform type_info = [ ('int', 'IntArray', 'NPY_INT'), ('unsigned int', 'UIntArray', 'NPY_UINT'), @@ -37,13 +38,11 @@ The numpy array may however be copied and used in any manner. # For malloc etc. from libc.stdlib cimport * -IF UNAME_SYSNAME == "Windows": - cdef extern from "msstdint.h" nogil: - ctypedef unsigned int uintptr_t -ELSE: - from libc.stdint cimport uintptr_t + +from libc.stdint cimport uintptr_t cimport numpy as np +np.import_array() import numpy as np @@ -71,12 +70,22 @@ cdef extern from "numpy/arrayobject.h": # memcpy cdef extern from "stdlib.h": - void *memcpy(void *dst, void *src, long n) nogil + void *memcpy(void *dst, void *src, long n) noexcept nogil + +# Cython>= 3.0 now disallows replacing the guts of a numpy array. +# Workaround: https://github.com/rainwoodman/pandas/blob/05d3fe2402e4563124e7060837ded7513ab5bca7/pandas/_libs/reduction.pyx#L27 # noqa: E501 +cdef extern from *: + """ + static void PyArray_SET_DATA(PyArrayObject *arr, char * data) { + arr->data = data; + } + """ + void PyArray_SET_DATA(np.ndarray arr, char * data) noexcept nogil # numpy module initialization call _import_array() -cdef inline long aligned(long n, int item_size) nogil: +cdef inline long aligned(long n, int item_size) noexcept nogil: """Align `n` items each having size (in bytes) `item_size` to 64 bytes and return the appropriate number of items that would be aligned to 64 bytes. @@ -96,7 +105,7 @@ cpdef long py_aligned(long n, int item_size): """ return aligned(n, item_size) -cdef void* _aligned_malloc(size_t bytes) nogil: +cdef void* _aligned_malloc(size_t bytes) noexcept nogil: """Allocates block of memory starting on a cache line. Algorithm from: @@ -113,7 +122,7 @@ cdef void* _aligned_malloc(size_t bytes) nogil: return result -cdef void* _aligned_realloc(void *existing, size_t bytes, size_t old_size) nogil: +cdef void* _aligned_realloc(void *existing, size_t bytes, size_t old_size) noexcept nogil: """Allocates block of memory starting on a cache line. """ @@ -126,7 +135,7 @@ cdef void* _aligned_realloc(void *existing, size_t bytes, size_t old_size) nogil return result -cdef void* _deref_base(void* ptr) nogil: +cdef void* _deref_base(void* ptr) noexcept nogil: cdef size_t cache_size = 64 # Recover where block actually starts cdef char* base = (ptr)[-1] @@ -135,13 +144,13 @@ cdef void* _deref_base(void* ptr) nogil: raise MemoryError("Passed pointer is not aligned.") return base -cdef void* aligned_malloc(size_t bytes) nogil: +cdef void* aligned_malloc(size_t bytes) noexcept nogil: return _aligned_malloc(bytes) -cdef void* aligned_realloc(void* p, size_t bytes, size_t old_size) nogil: +cdef void* aligned_realloc(void* p, size_t bytes, size_t old_size) noexcept nogil: return _aligned_realloc(p, bytes, old_size) -cdef void aligned_free(void* p) nogil: +cdef void aligned_free(void* p) noexcept nogil: """Free block allocated by alligned_malloc. """ free(_deref_base(p)) @@ -153,23 +162,23 @@ cdef class BaseArray: #### Cython interface ################################################# - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ pass - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: pass - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array self.length = 0 arr.dimensions[0] = self.length - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: pass - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: pass #### Python interface ################################################# @@ -399,7 +408,7 @@ cdef class ${CLASSNAME}(BaseArray): ##### Cython protocol ###################################### - cdef void c_align_array(self, LongArray new_indices, int stride=1) nogil: + cdef void c_align_array(self, LongArray new_indices, int stride=1) noexcept nogil: """Rearrange the array contents according to the new indices. """ @@ -428,7 +437,7 @@ cdef class ${CLASSNAME}(BaseArray): aligned_free(temp) - cdef void c_append(self, ${ARRAY_TYPE} value) nogil: + cdef void c_append(self, ${ARRAY_TYPE} value) noexcept nogil: cdef long l = self.length cdef PyArrayObject* arr = self._npy_array @@ -440,7 +449,7 @@ cdef class ${CLASSNAME}(BaseArray): # update the numpy arrays length arr.dimensions[0] = self.length - cdef void c_reserve(self, long size) nogil: + cdef void c_reserve(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL if size > self.alloc: @@ -458,14 +467,14 @@ cdef class ${CLASSNAME}(BaseArray): self.alloc = size arr.data = self.data - cdef void c_reset(self) nogil: + cdef void c_reset(self) noexcept nogil: BaseArray.c_reset(self) if self._old_data != NULL: self.data = self._old_data self._old_data = NULL - self._npy_array.data = self.data + PyArray_SET_DATA(self._npy_array, self.data) - cdef void c_resize(self, long size) nogil: + cdef void c_resize(self, long size) noexcept nogil: cdef PyArrayObject* arr = self._npy_array # reserve memory @@ -475,7 +484,7 @@ cdef class ${CLASSNAME}(BaseArray): self.length = size arr.dimensions[0] = self.length - cdef void c_set_view(self, ${ARRAY_TYPE} *array, long length) nogil: + cdef void c_set_view(self, ${ARRAY_TYPE} *array, long length) noexcept nogil: """Create a view of a given raw data pointer with given length. """ if self._old_data == NULL: @@ -487,7 +496,7 @@ cdef class ${CLASSNAME}(BaseArray): arr.data = self.data arr.dimensions[0] = self.length - cdef void c_squeeze(self) nogil: + cdef void c_squeeze(self) noexcept nogil: cdef PyArrayObject* arr = self._npy_array cdef void* data = NULL cdef size_t size = max(self.length, 16) diff --git a/cyarray/msstdint.h b/cyarray/msstdint.h deleted file mode 100644 index 4fe0ef9..0000000 --- a/cyarray/msstdint.h +++ /dev/null @@ -1,259 +0,0 @@ -// ISO C9x compliant stdint.h for Microsoft Visual Studio -// Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 -// -// Copyright (c) 2006-2013 Alexander Chemeris -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright -// notice, this list of conditions and the following disclaimer in the -// documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the product nor the names of its contributors may -// be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED -// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; -// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -/////////////////////////////////////////////////////////////////////////////// - -#ifndef _MSC_VER // [ -#error "Use this header only with Microsoft Visual C++ compilers!" -#endif // _MSC_VER ] - -#ifndef _MSC_STDINT_H_ // [ -#define _MSC_STDINT_H_ - -#if _MSC_VER > 1000 -#pragma once -#endif - -#if _MSC_VER >= 1600 // [ -#include -#else // ] _MSC_VER >= 1600 [ - -#include - -// For Visual Studio 6 in C++ mode and for many Visual Studio versions when -// compiling for ARM we should wrap include with 'extern "C++" {}' -// or compiler give many errors like this: -// error C2733: second C linkage of overloaded function 'wmemchr' not allowed -#ifdef __cplusplus -extern "C" { -#endif -# include -#ifdef __cplusplus -} -#endif - -// Define _W64 macros to mark types changing their size, like intptr_t. -#ifndef _W64 -# if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 -# define _W64 __w64 -# else -# define _W64 -# endif -#endif - - -// 7.18.1 Integer types - -// 7.18.1.1 Exact-width integer types - -// Visual Studio 6 and Embedded Visual C++ 4 doesn't -// realize that, e.g. char has the same size as __int8 -// so we give up on __intX for them. -#if (_MSC_VER < 1300) - typedef signed char int8_t; - typedef signed short int16_t; - typedef signed int int32_t; - typedef unsigned char uint8_t; - typedef unsigned short uint16_t; - typedef unsigned int uint32_t; -#else - typedef signed __int8 int8_t; - typedef signed __int16 int16_t; - typedef signed __int32 int32_t; - typedef unsigned __int8 uint8_t; - typedef unsigned __int16 uint16_t; - typedef unsigned __int32 uint32_t; -#endif -typedef signed __int64 int64_t; -typedef unsigned __int64 uint64_t; - - -// 7.18.1.2 Minimum-width integer types -typedef int8_t int_least8_t; -typedef int16_t int_least16_t; -typedef int32_t int_least32_t; -typedef int64_t int_least64_t; -typedef uint8_t uint_least8_t; -typedef uint16_t uint_least16_t; -typedef uint32_t uint_least32_t; -typedef uint64_t uint_least64_t; - -// 7.18.1.3 Fastest minimum-width integer types -typedef int8_t int_fast8_t; -typedef int16_t int_fast16_t; -typedef int32_t int_fast32_t; -typedef int64_t int_fast64_t; -typedef uint8_t uint_fast8_t; -typedef uint16_t uint_fast16_t; -typedef uint32_t uint_fast32_t; -typedef uint64_t uint_fast64_t; - -// 7.18.1.4 Integer types capable of holding object pointers -#ifdef _WIN64 // [ - typedef signed __int64 intptr_t; - typedef unsigned __int64 uintptr_t; -#else // _WIN64 ][ - typedef _W64 signed int intptr_t; - typedef _W64 unsigned int uintptr_t; -#endif // _WIN64 ] - -// 7.18.1.5 Greatest-width integer types -typedef int64_t intmax_t; -typedef uint64_t uintmax_t; - - -// 7.18.2 Limits of specified-width integer types - -#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 - -// 7.18.2.1 Limits of exact-width integer types -#define INT8_MIN ((int8_t)_I8_MIN) -#define INT8_MAX _I8_MAX -#define INT16_MIN ((int16_t)_I16_MIN) -#define INT16_MAX _I16_MAX -#define INT32_MIN ((int32_t)_I32_MIN) -#define INT32_MAX _I32_MAX -#define INT64_MIN ((int64_t)_I64_MIN) -#define INT64_MAX _I64_MAX -#define UINT8_MAX _UI8_MAX -#define UINT16_MAX _UI16_MAX -#define UINT32_MAX _UI32_MAX -#define UINT64_MAX _UI64_MAX - -// 7.18.2.2 Limits of minimum-width integer types -#define INT_LEAST8_MIN INT8_MIN -#define INT_LEAST8_MAX INT8_MAX -#define INT_LEAST16_MIN INT16_MIN -#define INT_LEAST16_MAX INT16_MAX -#define INT_LEAST32_MIN INT32_MIN -#define INT_LEAST32_MAX INT32_MAX -#define INT_LEAST64_MIN INT64_MIN -#define INT_LEAST64_MAX INT64_MAX -#define UINT_LEAST8_MAX UINT8_MAX -#define UINT_LEAST16_MAX UINT16_MAX -#define UINT_LEAST32_MAX UINT32_MAX -#define UINT_LEAST64_MAX UINT64_MAX - -// 7.18.2.3 Limits of fastest minimum-width integer types -#define INT_FAST8_MIN INT8_MIN -#define INT_FAST8_MAX INT8_MAX -#define INT_FAST16_MIN INT16_MIN -#define INT_FAST16_MAX INT16_MAX -#define INT_FAST32_MIN INT32_MIN -#define INT_FAST32_MAX INT32_MAX -#define INT_FAST64_MIN INT64_MIN -#define INT_FAST64_MAX INT64_MAX -#define UINT_FAST8_MAX UINT8_MAX -#define UINT_FAST16_MAX UINT16_MAX -#define UINT_FAST32_MAX UINT32_MAX -#define UINT_FAST64_MAX UINT64_MAX - -// 7.18.2.4 Limits of integer types capable of holding object pointers -#ifdef _WIN64 // [ -# define INTPTR_MIN INT64_MIN -# define INTPTR_MAX INT64_MAX -# define UINTPTR_MAX UINT64_MAX -#else // _WIN64 ][ -# define INTPTR_MIN INT32_MIN -# define INTPTR_MAX INT32_MAX -# define UINTPTR_MAX UINT32_MAX -#endif // _WIN64 ] - -// 7.18.2.5 Limits of greatest-width integer types -#define INTMAX_MIN INT64_MIN -#define INTMAX_MAX INT64_MAX -#define UINTMAX_MAX UINT64_MAX - -// 7.18.3 Limits of other integer types - -#ifdef _WIN64 // [ -# define PTRDIFF_MIN _I64_MIN -# define PTRDIFF_MAX _I64_MAX -#else // _WIN64 ][ -# define PTRDIFF_MIN _I32_MIN -# define PTRDIFF_MAX _I32_MAX -#endif // _WIN64 ] - -#define SIG_ATOMIC_MIN INT_MIN -#define SIG_ATOMIC_MAX INT_MAX - -#ifndef SIZE_MAX // [ -# ifdef _WIN64 // [ -# define SIZE_MAX _UI64_MAX -# else // _WIN64 ][ -# define SIZE_MAX _UI32_MAX -# endif // _WIN64 ] -#endif // SIZE_MAX ] - -// WCHAR_MIN and WCHAR_MAX are also defined in -#ifndef WCHAR_MIN // [ -# define WCHAR_MIN 0 -#endif // WCHAR_MIN ] -#ifndef WCHAR_MAX // [ -# define WCHAR_MAX _UI16_MAX -#endif // WCHAR_MAX ] - -#define WINT_MIN 0 -#define WINT_MAX _UI16_MAX - -#endif // __STDC_LIMIT_MACROS ] - - -// 7.18.4 Limits of other integer types - -#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 - -// 7.18.4.1 Macros for minimum-width integer constants - -#define INT8_C(val) val##i8 -#define INT16_C(val) val##i16 -#define INT32_C(val) val##i32 -#define INT64_C(val) val##i64 - -#define UINT8_C(val) val##ui8 -#define UINT16_C(val) val##ui16 -#define UINT32_C(val) val##ui32 -#define UINT64_C(val) val##ui64 - -// 7.18.4.2 Macros for greatest-width integer constants -// These #ifndef's are needed to prevent collisions with . -// Check out Issue 9 for the details. -#ifndef INTMAX_C // [ -# define INTMAX_C INT64_C -#endif // INTMAX_C ] -#ifndef UINTMAX_C // [ -# define UINTMAX_C UINT64_C -#endif // UINTMAX_C ] - -#endif // __STDC_CONSTANT_MACROS ] - -#endif // _MSC_VER >= 1600 ] - -#endif // _MSC_STDINT_H_ ] diff --git a/cyarray/tests/test_carray.py b/cyarray/tests/test_carray.py index 61f0bde..9c1f655 100644 --- a/cyarray/tests/test_carray.py +++ b/cyarray/tests/test_carray.py @@ -5,10 +5,10 @@ """ - # standard imports import unittest import numpy +import pytest # local imports from cyarray.carray import LongArray, py_aligned @@ -508,5 +508,348 @@ def test_reset_works_after_set_view(self): self.assertListEqual(view.get_npy_array().tolist(), expect) +class BenchmarkLongArray(unittest.TestCase): + """ + Tests for the LongArray class. + """ + + @pytest.fixture(autouse=True) + def setupBenchmark(self, benchmark): + self.benchmark = benchmark + + def test_constructor(self): + """ + Test the constructor. + """ + n = numpy.random.randint(low=10, high=100) + la = self.benchmark(LongArray, n) + + self.assertEqual(la.length, n) + self.assertEqual(la.alloc, n) + self.assertEqual(len(la.get_npy_array()), n) + + def test_set_indexing(self): + n = 100 + lab = LongArray(n) + self.benchmark.pedantic(lab.set, args=(9, n)) + self.assertEqual(lab[9], n) + + def test_get_indexing(self): + la = LongArray(100) + la[98] = 15 + res = self.benchmark(la.get, 98) + self.assertEqual(res, 15) + + def test_append(self): + lab = LongArray(0) + n = 100 + self.benchmark(lab.append, n) + self.assertEqual(lab[0], n) + + def test_reserve(self): + """ + Tests the reserve function. + """ + + def breserve(n): + la = LongArray(0) + la.reserve(n) + return la + + n = 100 + la = self.benchmark(breserve, n) + self.assertEqual(la.alloc, n) + + def test_resize(self): + """ + Tests the resize function. + """ + + def bresize(lab): + n = numpy.random.randint(low=10, high=20) + lab.resize(n) + return lab, n + + la = LongArray(10) + la, n = self.benchmark(bresize, la) + self.assertEqual(la.length, n) + + def test_get_npy_array(self): + la = LongArray(100) + la[0] = 1 + la[1] = 2 + la[2] = 3 + + nparray = self.benchmark(la.get_npy_array) + for i in range(3): + self.assertEqual(nparray[0], la[0]) + + def test_set_data(self): + """ + Tests the set_data function. + """ + n = 50 + la = LongArray(n) + np = numpy.arange(n) + self.benchmark(la.set_data, np) + + for i in range(n): + self.assertEqual(la[i], np[i]) + + self.assertRaises(ValueError, la.set_data, numpy.arange(55)) + + def test_squeeze(self): + + def bsqueeze(): + lab = LongArray(5) + lab.append(4) + lab.squeeze() + return lab + + la = self.benchmark(bsqueeze) + + self.assertEqual(la.length, 6) + self.assertEqual(la.alloc >= la.length, True) + self.assertEqual(len(la.get_npy_array()), 6) + + def test_reset(self): + def breset(): + lab = LongArray(5) + lab.reset() + return lab + + la = self.benchmark(breset) + + self.assertEqual(la.length, 0) + self.assertEqual(la.alloc, 5) + self.assertEqual(len(la.get_npy_array()), 0) + + def test_extend(self): + l2 = LongArray(5) + + for i in range(5): + l2[i] = 5 + i + + def bextend(l2n): + l1b = LongArray(0) + l1b.extend(l2n.get_npy_array()) + return l1b + + l1 = self.benchmark(bextend, l2) + + self.assertEqual(l1.length, 5) + self.assertEqual( + numpy.allclose( + l1.get_npy_array(), + numpy.arange(5, 10)), + True) + + def test_remove(self): + + def bremove(rem): + l1b = LongArray(10) + l1b.set_data(numpy.arange(10)) + l1b.remove(rem) + return l1b + + rem = [0, 4, 3] + l1 = self.benchmark(bremove, numpy.array(rem, dtype=int)) + + self.assertEqual(l1.length, 7) + self.assertEqual(numpy.allclose([7, 1, 2, 8, 9, 5, 6], + l1.get_npy_array()), True) + + def test_remove_with_strides(self): + + def bremove(rem): + l1b = LongArray(12) + l1b.set_data(numpy.arange(12)) + l1b.remove(rem, stride=3) + return l1b + + rem = [3, 1] + l1 = self.benchmark(bremove, numpy.array(rem, dtype=int)) + + # Then + self.assertEqual(l1.length, 6) + self.assertEqual(numpy.allclose([0, 1, 2, 6, 7, 8], + l1.get_npy_array()), True) + + # Given + l1 = LongArray(12) + l1.set_data(numpy.arange(12)) + + # When + rem = [0, 2] + l1.remove(numpy.array(rem, dtype=int), stride=3) + + # Then + self.assertEqual(l1.length, 6) + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] + self.assertEqual(numpy.allclose([9, 10, 11, 3, 4, 5], + l1.get_npy_array()), True) + + def test_align_array(self): + l1 = LongArray(10) + l1.set_data(numpy.arange(10)) + + new_indices = LongArray(10) + new_indices.set_data(numpy.asarray([1, 5, 3, 2, 4, 7, 8, 6, 9, 0])) + + l1.align_array(new_indices) + self.assertEqual(numpy.allclose([1, 5, 3, 2, 4, 7, 8, 6, 9, 0], + l1.get_npy_array()), True) + + # Test case with strides. + + def balign_array(): + l1b = LongArray(6) + l1b.set_data(numpy.arange(6)) + + new_indices = LongArray(3) + new_indices.set_data(numpy.asarray([2, 1, 0])) + l1b.align_array(new_indices, 2) + return l1b + + l1 = self.benchmark(balign_array) + self.assertEqual(numpy.allclose([4, 5, 2, 3, 0, 1], + l1.get_npy_array()), True) + + def test_copy_subset(self): + + def bcopy_subset(l2b): + l1b = LongArray(10) + l1b.set_data(numpy.arange(10)) + + # a valid copy. + l1b.copy_subset(l2b, 5, 9) + return l1b + + l2 = LongArray(4) + l2[0] = 4 + l2[1] = 3 + l2[2] = 2 + l2[3] = 1 + + l1 = self.benchmark(bcopy_subset, l2) + + self.assertEqual(numpy.allclose([0, 1, 2, 3, 4, 4, 3, 2, 1, 9], + l1.get_npy_array()), True) + + def test_copy_subset_works_with_strides(self): + def bcopy_subset(l2b): + l1b = LongArray(8) + l1b.set_data(numpy.arange(8)) + l1b.copy_subset(l2b, 2, 3, stride=2) + return l1b + + # Given + l2 = LongArray(4) + l2.set_data(numpy.arange(10, 14)) + + # When + l1 = self.benchmark(bcopy_subset, l2) + + # Then + numpy.testing.assert_array_equal( + l1.get_npy_array(), + [0, 1, 2, 3, 10, 11, 6, 7] + ) + + def test_copy_values(self): + def bcopy_values(l2b, indices): + l1b = LongArray(8) + l1b.set_data(numpy.arange(8)) + l1b.copy_values(indices, l2b) + return l1b + + # Given + l1 = LongArray(8) + l1.set_data(numpy.arange(8)) + l2 = LongArray(8) + l2.set_data(numpy.zeros(8, dtype=int)) + + # When + indices = LongArray(3) + indices.set_data(numpy.array([2, 4, 6])) + l1 = self.benchmark.pedantic(bcopy_values, args=(l2, indices)) + + # Then + numpy.testing.assert_array_equal( + l2.get_npy_array(), + [2, 4, 6] + [0] * 5 + ) + + def test_update_min_max(self): + """ + Tests the update_min_max function. + """ + + def bupdate_min_max(): + l1b = LongArray(10) + l1b.set_data(numpy.arange(10)) + l1b.update_min_max() + return l1b + + l1 = self.benchmark(bupdate_min_max) + + self.assertEqual(l1.minimum, 0) + self.assertEqual(l1.maximum, 9) + + def test_pickling(self): + """ + Tests the __reduce__ and __setstate__ functions. + """ + import pickle + + def bpickle(l1b): + l1_dump = pickle.dumps(l1b) + return pickle.loads(l1_dump) + + l1 = LongArray(3) + l1.set_data(numpy.arange(3)) + + l1_load = self.benchmark(bpickle, l1) + self.assertEqual( + (l1_load.get_npy_array() == l1.get_npy_array()).all(), True) + + def test_set_view(self): + # Given + src = LongArray() + src.extend(numpy.arange(5)) + + # When. + def bset_view(bsrc): + bview = LongArray() + bview.set_view(bsrc, 1, 4) + return bview + + view = self.benchmark(bset_view, src) + + # Then. + self.assertEqual(view.length, 3) + expect = list(range(1, 4)) + self.assertListEqual(view.get_npy_array().tolist(), expect) + + def test_set_view_for_empty_array(self): + # Given + src = LongArray() + src.extend(numpy.arange(5)) + + # When. + + def bset_view(bsrc): + view = LongArray() + view.set_view(bsrc, 1, 1) + return view + + view = self.benchmark(bset_view, src) + + # Then. + self.assertEqual(view.length, 0) + expect = [] + self.assertListEqual(view.get_npy_array().tolist(), expect) + + if __name__ == '__main__': unittest.main() diff --git a/pyproject.toml b/pyproject.toml index b53d030..5b68acd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "wheel>=0.29.0", "setuptools>=42.0.0", - "oldest-supported-numpy", - "Cython<3.0", + "numpy", + "Cython", "mako" -] \ No newline at end of file +] diff --git a/requirements.txt b/requirements.txt index 64cd771..0926b8a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -Cython<3.0 +Cython setuptools>=6.0 numpy mako pytest +pytest-benchmark[histogram] diff --git a/setup.py b/setup.py index fe1a5d2..3801e83 100644 --- a/setup.py +++ b/setup.py @@ -87,9 +87,9 @@ def setup_package(): # The requirements. install_requires = [ - 'numpy', 'mako', 'Cython<3.0', 'setuptools>=6.0' + 'numpy', 'mako', 'Cython', 'setuptools>=6.0' ] - tests_require = ["pytest"] + tests_require = ["pytest", "pytest-benchmark[histogram]"] docs_require = ["sphinx"] ext_modules = get_basic_extensions()