cython cast char指向numpy短裤阵列

时间:2017-06-18 18:35:02

标签: python arrays performance numpy cython

我正在努力改进添加两个固定长度数组所花费的时间。我必须将2个字节串转换为2个固定长度的短数组,然后将这两个数组加在一起,最后将结果数组作为字节串输出。

目前我有:

import cython
cimport numpy as np
import numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
def cython_layer( char* c_string1, char* c_string2, int length ):
    cdef np.ndarray[ np.int16_t, ndim=1 ] np_orig = np.fromstring( c_string1[:length], np.int16, count=length//2 )
    cdef np.ndarray[ np.int16_t, ndim=1 ] np_new  = np.fromstring( c_string2[:length], np.int16, count=length//2 )
    res = np_orig + np_new
    return res.tostring() 

然而,更简单的numpy方法产生非常相似(更好)的性能:

def layer(self, orig, new, length):
    np_orig = fromstring(orig, np.int16, count=length // 2)
    np_new  = fromstring(new,  np.int16, count=length  // 2)
    res     = np_orig + np_new 
    return res.tostring()

对于这个简单的例子,是否有可能改善numpy速度?我的直觉是肯定的,但我对Cython的处理力度不够。使用Ipython %timeit魔法我已经将函数计时:

100000 loops, best of 3: 5.79 µs per loop    # python + numpy
100000 loops, best of 3: 8.77 µs per loop    # cython + numpy

e.g:

a = np.array( range(1024), dtype=np.int16).tostring()
layer(a,a,len(a)) == cython_layer(a,a,len(a))
# True
%timeit layer(a, a, len(a) )
# 100000 loops, best of 3: 6.06 µs per loop
%timeit cython_layer(a, a, len(a))
# 100000 loops, best of 3: 9.19 µs per loop

编辑:更改layer以显示size=len(orig)//2 orig和new都是长度为2048的字节数组。将它们转换为short(np.int16)会产生大小为1024的输出数组。 / p> 编辑2:我是个白痴。

edit3:行动中的例子

1 个答案:

答案 0 :(得分:2)

一种解决方案是跳过numpy数组,只使用C指针:

from cpython.bytes cimport PyBytes_FromStringAndSize
from libc.stdint cimport int16_t

def layer2(char* orig, char* new, length):
    cdef:
        bytes res = PyBytes_FromStringAndSize(NULL,2*(length//2))
        char* res_as_charp = res
        int16_t* orig_as_int16p = <int16_t*>orig
        int16_t* new_as_int16p = <int16_t*>new
        int16_t* res_as_int16p = <int16_t*>res_as_charp       
        Py_ssize_t i


    for i in range(length//2):
        res_as_int16p[i] = orig_as_int16p[i] + new_as_int16p[i]

    return res

基本上,我使用C API函数PyBytes_FromStringAndSize为结果创建一个空字符串并对其进行修改。这样做的好处是,与您的版本不同,输入和输出都按原样使用而不是复制。请注意,允许您修改此类Python字符串的情况是您刚刚使用PyBytes_FromStringAndSize(NULL,length) - this is in the C API documentation创建新字符串时。

然后我得到一个char*(不复制数据,只指向现有数据)。

然后我将char*转换为两个输入,输出为int16_t* - 这只会改变内存的解释方式。

然后我循环遍历数组,进行添加并使用指针索引。

就速度而言,这比短字符串(length<100)的Python实现快8倍。这主要是由于函数调用的固定Python开销正在创建我相信的numpy数组。对于较长的字符串(length>=100000),我的版本实际上稍微慢一些。我怀疑numpy有一个更好的矢量化/并行化循环用于添加。

额外备注

显示的代码是Python 3的表单 - 对于Python 2,您需要PyString_...而不是PyBytes_...

使用np.frombuffer代替np.fromstring,您的纯Python版本可以略微提升(约10-20%)。这样可以避免复制输入。