使用Cython构建长度未知的一维数组/列表/向量的最有效方法?还是永远不要这样做?

2024-10-05 12:13:54 发布

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

我用Cython写了一个时间临界模型。我的Cython扩展的主函数有一个循环,根据Cython profiler(它以黄色阴影显示Python调用的数量),当前唯一的“黄色”部分是我附加到Python列表的地方。(在Python脚本中调用Cython函数时,必须输出Python对象)。这是我的函数的基本思想(其余的都是多余的,我已经测试了这个函数的每个部分,追加操作是瓶颈):

from libc.math cimport log
def main(some args):
    cdef (some vars)

    cdef list OutputList = []

    # NB: all vars have declared types
    for x in range(t):
        (do some Cythonic stuff, some of which uses my cimport-ed log)
        if condition is True:
            OutputList.append(x) # this is the only 'yellow' line in my main loop.
    return OutputList # return Python object to Python script that calls main()

不幸的是,我不知道输出数组/列表/向量的长度(不管我最终使用什么)。但是,我可以将其设置为52560,这就是我在其他一些Python代码中将其调整为下行的原因。我想在不设置输出数组长度的情况下获得一个大的速度提升,但是如果它阻碍了我,我会很高兴地放弃这个希望。

我也尝试过用C++在Cython使用C++数据结构(向量、队列等),但是这样做能消除我的良好的导入日志的能力。我在Cython文档/ wiki上看到,你可以编写一个“SIMM”模块来使用C++ Cython中的PURE C函数,但是我不知道如何去做这个,而且我找不到任何关于如何去做的任何东西。

无论如何,我欢迎所有支持我的问题的建议:

在Cython中构建未知大小的列表/数组/向量的最佳方法是什么?或者有一个明确的替代方案(比如用一个已知长度的iterable对象来解决)来解决我的未知长度问题吗?

更新

C++容器确实显示了相对于项目分配的速度增加,并且项目赋值在附加到列表和NUMPY数组上显示出速度增加。最好的方法是使用C++容器,同时也可以导入PURE C函数…这会阻止从LBC.MaX中查看快速日志函数的速度。


Tags: 对象函数log列表mainsome数组vars
3条回答

在CPython中,追加python列表是一个很好的优化操作。Python不为每个元素分配内存,而是递增地增加指向列表中对象的指针数组。所以换成Cython对你没什么帮助。

您可以在Cython中使用c++容器,如下所示:

from libc.math cimport log
from libcpp.list cimport list as cpplist

def main(int t):

    cdef cpplist[int] temp

    for x in range(t):
        if x> 0:
            temp.push_back(x)

    cdef int N = temp.size()
    cdef list OutputList = N*[0]

    for i in range(N):
        OutputList[i] = temp.front()
        temp.pop_front()

    return OutputList  

你必须测试一下这是否会加快速度,但也许你不会获得太多的速度。

另一种方法是使用numpy数组。在这里,Cython非常擅长优化代码。因此,如果您可以使用numpy数组作为main的返回值,那么您应该考虑这样做,并用一些Cython代码分配和填充numpy数组来替换OutputList的构造和填充。

有关详细信息,请参见http://docs.cython.org/src/tutorial/numpy.html

问问你是否需要帮助。

更新:如果避免在两个循环中查找方法,则代码应该快一点:

from libc.math cimport log
from libcpp.list cimport list as cpplist

def main(int t):

    cdef cpplist[int] temp

    push_back = temp.push_back
    for x in range(t):
        if x> 0:
            push_back(x)

    cdef int N = temp.size()
    cdef list OutputList = N*[0]

    front = temp.front()
    pop_front = temp.pop_front()
    for i in range(N):
        OutputList[i] = front()
        pop_front()

    return OutputList  

您可以做的是计算有多少元素符合您的条件,然后为这些元素分配一个足够大的numpy数组。

# pseudo code
def main(): 
   count = 0
   for i in range(t):
       if criteria: 
            count += 1

   cdef numpy.ndarray[count] result

   int idx =0
   for i in range(t):
      if criteria:
          idx += 1
          result[idx] = value

build1darray.pyx

#cython: boundscheck=False, wraparound=False
from libc.math cimport log

from cython.parallel cimport prange

import numpy as pynp
cimport numpy as np

# copy declarations from libcpp.vector to allow nogil
cdef extern from "<vector>" namespace "std":
    cdef cppclass vector[T]:
        void push_back(T&) nogil
        size_t size()
        T& operator[](size_t)

def makearray(int t):
    cdef vector[np.float_t] v
    cdef int i
    with nogil: 
        for i in range(t):
            if i % 10 == 0:
                v.push_back(log(i+1))

    cdef np.ndarray[np.float_t] a = pynp.empty(v.size(), dtype=pynp.float)
    for i in prange(a.shape[0], nogil=True):
        a[i] = v[i]
    return a

第二部分是第一个循环的~1%,因此在这种情况下,对其速度进行优化是没有意义的。

<math.h>在我的系统上有extern "C" { ... }所以libc.math.log工作。

可以使用PyArray_SimpleNewFromData()来避免复制数据,从而为数组管理内存。

相关问题 更多 >

    热门问题