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

问题描述 投票:0回答:3

我有一个用 Cython 编写的时间关键模型。我的 Cython 扩展的主要函数有一个循环,根据 Cython 分析器(其中以黄色阴影显示 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 代码中调整它的大小的原因。我希望在不设置输出数组长度的情况下获得显着的速度提升,但如果它阻碍了我,我会很乐意放弃这个希望。

我还尝试在 Cython 中使用 C++ 来使用 C++ 数据结构(向量、队列等),但这样做会消除我很好地导入日志的能力。我在 Cython 文档/wiki 上看到,您可以编写一个“shim”模块来在 C++ Cython 中使用纯 C 函数,但我不知道如何执行此操作,也找不到有关如何执行此操作的任何信息。

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

在 Cython 中构建未知大小的列表/数组/向量的最佳方法是什么?或者是否有一个明确的替代方案(例如使用已知长度的可迭代对象进行解决)使我的未知长度问题变得毫无意义?

更新

C++ 容器确实显示出比项目分配更快的速度,而项目分配也确实显示出比附加到列表和 numpy 数组的速度更快。最好的方法是使用 C++ 容器,同时还能够导入纯 C 函数...这将防止因必须在 libc.math 之外寻找快速日志函数而导致速度变慢。

python c cython
3个回答
6
投票

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()
可用于避免复制数据,从而节省您自己管理阵列内存的成本。


4
投票

追加 python 列表是 CPython 中一项优化良好的操作。 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 的返回值,那么您应该考虑这一点,并通过分配和填充 numpy 数组的一些 Cython 代码来替换

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  

0
投票

您可以做的是计算有多少元素满足您的条件,然后为这些元素分配一个足够大的 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
© www.soinside.com 2019 - 2024. All rights reserved.