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

2024-09-30 02:33:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我尝试在cython中通过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

但是,我从cdef int *pa = < int*>a得到了“Python对象不能转换为基元类型的指针”。如何使bsearch与python对象一起工作?在


Tags: 对象函数fromreturnarraycythonintarr
2条回答

您正在将对象强制转换为指针类型。这只是给您一个地址,如the Cython docs中所述:

To get the address of some Python object, use a cast to a pointer type like <void*> or <PyObject*>.

int*也是一个指针类型,因此实际上并没有将Python array对象转换为真正的C数组。相反,您正在(尝试)将其转换为指向实际指向Python对象的无效int指针。Cython认识到这是非法的并阻止了它(这比C要慷慨得多,后者只允许强制转换,然后可能在运行时崩溃)。在

正确的方法是使用类型化内存视图,正如文档在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']: ...代码,因为:

If you are using Python arrays instead of numpy arrays, you don’t need to check if the data is stored contiguously as this is always the case. See Working with Python arrays.

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

Kevin的解决方案是安全的,在更大的项目中应该是默认的,在这个项目中,您只是不知道您的函数将如何使用-因此,知道底层缓冲区是锁定的并且不能从另一个线程添加任何元素-这意味着我们传递给C例程的指针不会失效。在

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

def Indexer(int[:] a):

这个答案试图回答这样一个问题:与使用array.array的不安全解决方案相比,类型化内存视图的开销是多少。为此,我们考虑以下示例,它更简单,但计时与原始函数相似:

^{pr2}$

现在:

>>> 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称为无开销,而将{}称为{},但差别并不大(甚至不清楚是否存在!)与不安全的使用相比。在

如果我们将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的两倍,因此{}似乎是轻量级任务的更好选择。在

我的结论是:使用不安全的版本可能是值得的,但是我只会在确定类型化内存视图的开销确实是瓶颈并且只有一个线程的情况下才会这么做。在

相关问题 更多 >

    热门问题