Найти индекс целого числа в массиве в Cython - PullRequest
0 голосов
/ 09 июня 2018

Я пытаюсь реализовать функцию bsearch в cython поверх массива объектов python (который в конечном итоге будет передан той же функции).Код на данный момент:

# cython: language_level=3
from cpython cimport array as arr
cimport cython
import array
from libc.stdlib cimport bsearch

cdef int CustCmp( const void *a, const void *b ) with gil:    
   cdef int a_v = (< int*>a)[0]
   cdef int b_v = (< int*>b)[0]

   if a_v < b_v: return -1
   elif b_v < a_v: return 1
   else: return 0

def Indexer():
    cdef arr.array a = arr.array('I',(3,3,4,7,7,7,7,7,8,9))
    cdef int *pa = < int*>a
    cdef int x = 7
    cdef int *p  = < int*>bsearch( &x, pa, 10, sizeof( int ), &CustCmp )

    if ( p != NULL ):
        print( "{0}".format(p-pa) )
        return p-pa
   else:
        return -1

Однако я получаю «Объекты Python не могут быть преобразованы в указатели примитивных типов» из cdef int *pa = < int*>a.Что я должен сделать, чтобы bsearch работал с объектом python?

Ответы [ 2 ]

0 голосов
/ 12 июня 2018

Решение Кевина безопасно и должно быть по умолчанию в более крупном проекте, где вы просто не знаете, как будет использоваться ваша функция - поэтому полезно знать, что базовый буфер заблокирован и нет элементовможет быть добавлен в него из другого потока - это означает, что указатель, который мы передаем подпрограмме C, не будет признан недействительным.

Если бы функция имела подпись с типизированным представлением памяти, она бы даже быламожно использовать его для array.array и других буферов, таких как numpy-массивы, например:

def Indexer(int[:] a):

Этот ответ пытается ответить на вопрос, каковы издержки просмотра типизированной памяти по сравнению с небезопасным решением сarray.array.Для этого мы рассмотрим следующий пример, он проще, но временные характеристики похожи на исходную функцию:

%%cython
cimport cython
import array
from cpython cimport array

def with_array(array.array a):
    cdef int *pa = a.data.as_ints #I used wrong syntax in my comments sorry for that!
    return pa[0]

def direct_memview(int[::1] a):
    return a[0]

def create_memview(a):
    cdef int[:] pa=a
    return a[0]

А теперь:

>>> import array
>>> a=array.array('i',range(1000))
>>> %timeit with_array(a)
160 ns ± 8.62 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>> %timeit direct_memview(a)
706 ns ± 22.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

>>> %timeit create_memview(a)
732 ns ± 19.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Так что в основном безопасная функцияЧем в 4 раза медленнее, тем меньше становится, тем больше работы выполняется в функции.

Еще одно интересное наблюдение: документация относится к direct_memory как без издержек и к create_memview как overhead, но разница не так уж велика (даже неясно,существует!) по сравнению с небезопасным использованием.

Разница будет еще больше, если мы передадим numpy-массивам функции:

>>> import numpy as np
>>> b=np.array(a, dtype=np.int32)
>>> %timeit direct_memview(b)
1.48 µs ± 64.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> %timeit create_memview(b)
1.54 µs ± 28.6 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Служебные значения numpy-массивов в два раза больше, чем array.array, так что array.array кажется лучшим выбором для облегченной задачи.

Мой вывод от этого: возможно, стоит использовать небезопасную версию, но я бы сделал это, только если уверенчто представление типизированной памяти действительно является узким местом и существует только один поток.

0 голосов
/ 09 июня 2018

Вы приводите свои объекты к типам указателей.Это просто дает вам адрес, как указано в Документах Cython :

Чтобы получить адрес некоторого объекта Python, используйте приведение к типу указателя, например <void*> или<PyObject*>.

int* также является типом указателя, поэтому вы фактически не конвертируете объект Python array в массив bona fide C.Вместо этого вы (пытаетесь) преобразовать его в недопустимый указатель на int, который фактически указывает на объект Python.Cython признает, что это незаконно, и предотвращает это (это намного более щедро, чем C, который просто разрешил бы приведение, а затем, возможно, потерпел бы крах во время выполнения).

«Правильный» способ сделать это - использоватьтипизированное представление памяти, как подробно описано в документации в Передача данных из функции C через указатель .Но TL; DR должен написать что-то вроде этого:

cdef int[:] pa = a
cdef int *p  = < int*>bsearch( &x, &pa[0], 10, sizeof( int ), &CustCmp )

Обратите внимание, что код if not pa.flags['C_CONTIGUOUS']: ..., показанный в документации, может быть опущен, потому что:

Если выиспользуя массивы Python вместо массивов numpy, вам не нужно проверять, хранятся ли данные непрерывно, поскольку это всегда так.См. Работа с массивами Python .

Наконец, вам , вероятно, не нужно with gil для функции сравнения, поскольку я не вижу, чтобы она выполняла что-либо, чтотребует GIL.

...