Cython使用gmp算法

时间:2018-01-25 16:16:32

标签: python jupyter-notebook stdout cython

我正在尝试使用Jupyter笔记本(我使用python 2)并使用gmp算法在cython中实现一个简单的代码,以便处理非常大的整数。我不是gmp / cython专家。我的问题是:如何在函数fib()中打印 a 值。

以下代码返回{}。 我可以理解它与stdout有关。例如,我尝试了gmp_printf,但它没有用。

%%cython --link-args=-lgmp

cdef extern from "gmp.h":
    ctypedef struct mpz_t:
        pass

    cdef void mpz_init(mpz_t)  
    cdef void mpz_init_set_ui(mpz_t, unsigned int)

    cdef void mpz_add(mpz_t, mpz_t, mpz_t)
    cdef void mpz_sub(mpz_t, mpz_t, mpz_t)
    cdef void mpz_add_ui(mpz_t, const mpz_t, unsigned long int)

    cdef void mpz_set(mpz_t, mpz_t)

    cdef void mpz_clear(mpz_t)
    cdef unsigned long int mpz_get_ui(mpz_t)

    cdef void mpz_set_ui(mpz_t, unsigned long int)

    cdef int gmp_printf (const char*, ...)
    cdef size_t mpz_out_str (FILE , int , const mpz_t)

def fib(unsigned long int n):
    cdef mpz_t a,b
    mpz_init(a)
    mpz_init(b)
    mpz_init_set_ui(a,1)
    mpz_init_set_ui(b,1)
    cdef int i
    for i in range(n):        
        mpz_add(a,a,b)
        mpz_sub(b,a,b)
    return a

结果

fib(10)
{}

如果我使用return mpz_get_ui(a)代替return a 代码工作正常,但这不是我真正想要的东西(获得一个长整数)。

EDIT。 我在cython中再次将之前的代码与另一个代码进行了比较,但没有使用mpz。

%%cython
def pyfib(unsigned long int n):
    a,b=1,1
    for i in range(n):
        a=a+b
        b=a-b
    return a

最后是相同的代码,但是使用了gmpy2的mpz

%%cython
import gmpy2
from gmpy2 import mpz
def pyfib_with_gmpy2(unsigned long int n):
    cdef int i
    a,b=mpz(1),mpz(1)
    for i in range(n):
        a=a+b
        b=a-b
    return a    

然后

timeit fib(700000)
1 loops, best of 3: 3.19 s per loop

timeit pyfib(700000)
1 loops, best of 3: 11 s per loop

timeit pyfib_with_gmpy2(700000)
1 loops, best of 3: 3.28 s per loop

1 个答案:

答案 0 :(得分:1)

(答案主要是总结一堆评论)

您遇到的直接问题是Python没有真正的方法来处理C结构。为了解决这个问题,Cython尝试在将结构传递给Python时将结构转换为字典(如果可能的话)。在这种特殊情况下,mpz_t被C(因而是Cython)视为“不透明”,所以你不应该知道它的成员。因此,Cython“有帮助”将其转换为空字典(它所知道的所有成员的正确表示)。

对于立即修复我建议使用gmpy库,这是GMP的现有Python / Cython包装。这可能是比重复包装更好的选择。

作为这类问题的一般解决方案,有两个明显的选择。

  1. 您可以创建cdef wrapper class。我链接的文档是针对C ++的,但这个想法也可以应用于C(new /'del'替换为'malloc'/'free')。这最终是一个Python类(因此可以从Cython返回到Python)但包含一个C结构,您可以直接在Cython中操作它。该方法记录良好,不需要在此重复。

  2. 您可以在函数末尾将mpz_t转换回Python整数。我觉得这最有意义,因为它们最终代表着同样的东西。下面显示的代码是粗略的大纲,尚未经过测试(我没有安装gmp):

    cdef mpz_to_py_int(mpz_t x):
        # get bytes that describe the integer
        cdef const mp_limb_t* x_data = mpz_limbs_read(x)
        # view as a unsigned char* (i.e. as bytes)
        cdef unsigned char* x_data_bytes = <unsigned char*>x_data
        # cast to a memoryview then pass that to the int classmethod "from_bytes"
        # assuming big endian (python 3.2+ required)
        out = int.from_bytes(<unsigned char[:mpz_size(x):1]>x_data_bytes,'big')
    
        # correct using sign
        if mpz_sign(x) < 0:
           return -out
        else
           return out