在cython中查找数组中的整数索引

时间:2018-06-09 18:00:11

标签: python cython

我尝试在python上通过python对象数组实现一个bsearch函数(最终会传递给同一个函数)。到目前为止的代码是:

# 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

但是,我从“{1}}得到”Python对象无法转换为原始类型的指针“。我该怎么做才能使bsearch与python对象一起工作?

2 个答案:

答案 0 :(得分:3)

您正在将对象转换为指针类型。这只是为您提供了地址,如the Cython docs中所述:

  

要获取某些Python对象的地址,请使用强制类型转换为指针类型<void*><PyObject*>

int*也是指针类型,因此您实际上并未将Python array对象转换为真正的 C数组。相反,您(尝试)将其转换为实际指向Python对象的无效指针到int。 Cython认识到这是非法的并且会阻止它(这比C更慷慨,它只会允许演员然后在运行时崩溃)。

&#34;正确&#34;这样做的方法是使用类型化的内存视图,正如文档在Pass data from a C function via pointer中详细描述的那样。但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数组,则无需检查数据是否连续存储,因为总是如此。请参阅Working with Python arrays

最后,可能在比较器函数上不需要with gil,因为我看不到它在做任何需要GIL的事情。

答案 1 :(得分:0)

Kevin的解决方案是安全的,并且应该在更大的项目中默认,你不知道你的函数将如何被使用 - 所以知道底层缓冲区被锁定而没有元素是一个优点可以从另一个线程添加到它 - 这意味着我们传递给C例程的指针不会被无效。

如果函数具有带有类型化内存视图的签名,甚至可以将它用于array.array和其他缓冲区,例如numpy-arrays,例如:

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倍,显然它在功能中完成的工作越少越少。

另一个有趣的观察结果:documentationdirect_memory称为无开销,将create_memview称为overhead,但差异不大(甚至不清楚)是否存在!)与不安全的用法相比。

如果我们将numpy-arrays传递给函数,差异就更大了:

>>> 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)

array.array相比,numpy-arrays的开销是两倍,因此array.array似乎是轻量级任务的更好选择。

我对此的看法:使用不安全的版本可能是值得的,但是如果我确定键入的内存视图的开销确实是瓶颈并且只有一个线程,我只会这样做。