【问题标题】:How to cast a dynamic array to a list in Cython?如何将动态数组转换为 Cython 中的列表?
【发布时间】:2021-12-24 02:19:10
【问题描述】:

我正在实施一个简单的 Eratosthenes 筛子。当我为结果创建一个恒定大小的堆栈数组时,这相当简单,但是这种方法会限制我可以计算的素数的数量。

所以我尝试使用一些动态堆数组;这里的问题是,在“cpdef”函数中,我想返回一个 python 列表,我必须将数组转换为一个列表,但这是不可能的。

如果我在没有取消引用的情况下执行此操作,Cython 会抱怨“Python 对象不能从原始类型的指针转​​换”,如果我取消引用数组,它将返回第一个元素,即不足为奇。

我知道 NumPy 或使用了一些列表解析,但我不想降低性能或使用第三方包

考虑到限制,最快的方法是什么? CPython 数组很慢:可能是类型化的 memoryview?为什么 Cython 不能转换动态数组?

# cython: boundscheck=False
# cython: wraparound=False

from libc.stdlib cimport malloc, free
from libc.math cimport sqrt
from cython.operator cimport dereference

cdef inline int is_prime(unsigned int num, unsigned int *primes, unsigned int counter):
    cdef unsigned int i
    for i in range(counter):
        if num % primes[i] == 0:
            return 0
        if primes[i] > <unsigned int> sqrt(num) + 1:
            break
    return 1


cpdef list primes_below(unsigned int x):

    # cdef:
    #    unsigned int primes[1_000_000]
    #    unsigned int counter = 0
    #    unsigned int i
    #
    # for i in range(2, x):
    #    if is_prime(i, primes, counter):
    #        primes[counter] = i
    #        counter += 1

    # return (<list> primes)[:counter]

    
    # Alternative approach: (It doesn't work!)
    
    cdef:
        unsigned int *primes = <unsigned int *> malloc(sizeof(int) * x)
        unsigned int counter = 0
        unsigned int i
        
    for i in range(2, x):
        if is_prime(i, primes, counter):
            primes[counter] = i
            counter += 1

    return <list> primes
    # return <list> dereference(primes)

P.S.:我是 C 新手,所以我可能会遗漏一些细微的细节。


编辑: 首先,我应该感谢 @DavidW 对 Cython 问题的无尽全面回答。其次,他是对的!

列表理解真的很快

与我的想法相反。

这是新的实现:

# cython: boundscheck=False
# cython: wraparound=False
# cython: cdivision=True

from cython.view cimport array as cy_array
from libc.math cimport sqrt

cdef inline int is_prime(unsigned int num, unsigned int[::1] primes, unsigned int counter):
    cdef unsigned int i
    for i in range(counter):
        if num % primes[i] == 0:
            return 0
        if primes[i] > <unsigned int> sqrt(num) + 1:
            break
    return 1


cpdef list primes_below(unsigned int x):
    cdef:
        unsigned int[::1] primes = cy_array(shape=(x,), itemsize=sizeof(int), format="I")
        unsigned int counter = 0
        unsigned int i
        
    for i in range(2, x):
        if is_prime(i, primes, counter):
            primes[counter] = i
            counter += 1

    return [primes[i] for i in range(counter)]

我发现 Cython 数组既快速又简单,因此使用 malloc 分配内存是多余的。

【问题讨论】:

    标签: python c cython


    【解决方案1】:

    为什么 Cython 不能转换动态数组?

    怎么可能?它不知道你的数组有多长。它所知道的是,它是指向应该被解释为整数的一些内存的指针。即使它足够聪明地查看 malloc 也无济于事 - 你实际上填充的内存更少。

    我知道 NumPy 或使用一些列表解析,但我不想降低性能或使用第三方包。考虑到限制,最快的方法是什么? CPython 数组很慢;

    您是否实际衡量过您提出的任何这些性能声明?分配array.array 比使用malloc 稍慢,但访问它非常快(使用内存视图)。

    既然你必须在最后复制到一个列表中,为什么不直接追加到一个列表中并跳过“快速”C 数组呢?它可能比最后复制要快。

    unsigned int *primes = &lt;unsigned int *&gt; malloc(sizeof(int) * x)

    [...]

    P.S:我是 C 新手;所以别管我了。谢谢! :)

    这里的内存泄漏 - 你malloced 的内存永远不是freed。使用 Python 容器的一大优势是您不必考虑 C 内存分配


    要真正回答您的问题:最简单的方法可能是创建数组的临时 Cython 内存视图,然后将其传递给 list

    return list(<unsigned int[:counter:1]>primes)
    

    【讨论】:

    • 谢谢,@DavidW。实际上,我已经测试了三种不同的想法。将素数设置为 C 堆数组、列表和 array.array(无内存视图)。它需要 1x、8x 和 12x。现在我通过unsigned int[:] primes = &lt;unsigned int[:x] *&gt; malloc(sizeof(int) * x) 定义素数并返回list(primes[:counter]),这非常快。基于这个解决方案,我有 3 个问题:1) 有没有其他明显更快的实现? 2)我应该(如果是,如何)释放分配的内存? 3) 为什么返回&lt;list&gt; (primes[:counter]) 会引发错误?那不应该和list(...)一样吗
    • 您的结果取决于大小,但对我而言,array.array with memoryview 类似于malloc(因为您不必在最后浪费时间进行转换)。 array.array 没有内存视图虽然很慢。问题: 1)我会尝试列表理解 - Cython 通常可以很好地优化那些; 2)你绝对应该释放它。通常的方法是在finally 中使用free(pointer)try 块。 3) 不一样 - &lt;list&gt; 依赖于 Cython 有一些硬编码的强制转换逻辑,而 list(...) 是一个 Python 函数调用。
    • 谢谢,@DavidW。我看到了你对 Cython 问题的回答,这令人瞠目结舌!它实际上是无穷无尽的。你应该认真考虑写一本关于 Cython 的书!有一本书,它是一本好书,但我很想读另一本书,里面有新的、更多的例子和解释!我会更改问题以提及您是对的,并且列表理解确实很快。我将它们视为一种为了性能而必须避免的慢速 Python 语法,我的最后一个问题是我应该在哪里使用 而不是 list()?谢谢。 :)
    • “我应该在哪里使用 而不是 list()” - 我认为很少。我认为可能支持与某些 C++ 容器的转换,但主要是这样
    猜你喜欢
    • 2012-07-26
    • 2019-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-26
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    相关资源
    最近更新 更多