V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
black11black
V2EX  ›  Python

Cython 中如何调用 c++ 的模板库?

  •  
  •   black11black · 2020-12-02 01:06:39 +08:00 · 3009 次点击
    这是一个创建于 1234 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如题,最近有一个大量用到 python 中字典和列表寻址特性的科学计算函数,初步估算了一下循环次数在十亿级,纯 python 跑的非常慢,换到 pypy 以后加速快了三倍左右,但还是要等一分钟才能出结果。

    于是想到能不能用 cython 加速,我以前 cython 都是了解皮毛,只跑过 helloworld 。 今天初步研究了一下感觉可行,按我的想法,因为要大量使用 python 列表,其加速方式大概就是要映射到 cpp 的 vector 了吧

    在引入 stl 时遇到了无法引入的问题,有没有带佬指点一下哪错了

    # 按我的理解安装 cython 时应该已经自带转换好的 stl 文件了吧
    # 以下这段 C 库的引入是可以正常编译的
    import cython
    from libc.stdlib cimport atoi
    from libc.string cimport memset
    
    # 但是这里引入 CPP 的话却会报错
    from libcpp.vector cimport vector
    

    另外求问一下关于 python 字典的映射方式,在 cython 中应该怎么处理。比如现在有一个长度不确定的字典 name_dict,估测长度在一百万左右, 我需要大量使用诸如 name_dict['tom'] , name_dict['sam'] 这类字符串寻址来搜寻具体对象,对象内容不复杂,可以映射为结构体,但是这个字典该怎么搞?

    谢谢大家

    第 1 条附言  ·  2020-12-02 06:00:52 +08:00

    贴个条,载入的问题通过在文件头声明

    # distutils: language=c++
    

    解决了,个人觉得这个解决方法非常神秘。

    但是引入之后,简单测试累加1-100万,cython代码给出了错误的计算结果,这可能是什么原因导致的?应该不是变量类型的问题吧,长度没有超过。

    简单代码:

    import cython
    from libcpp.vector cimport vector
    
    def main():
    	cdef vector[int] vect # 测试stl
    	cdef int i
    	cdef long s = 0
    	for i in range(1000000):
    		sum += i 
            return s
    

    给出计算结果:1783293664,神秘

    第 2 条附言  ·  2020-12-02 06:03:44 +08:00

    上一条代码有错,重打

    # distutils: language=c++
    import cython
    from libcpp.vector cimport vector
    
    def main():
    	# 测试stl
    	cdef vector[int] vect 
    	# 测试计算
    	cdef int i
    	cdef long s = 0
    	for i in range(1000000):
    		s += i 
            return s
    
    25 条回复    2020-12-03 03:27:42 +08:00
    Tony042
        1
    Tony042  
       2020-12-02 03:44:27 +08:00
    考虑下 pybind11 来进行 python 和 C++交互?
    lovestudykid
        2
    lovestudykid  
       2020-12-02 04:40:20 +08:00
    应该是 cimport cython 吧,报错也不说什么错这怎么 debug
    lovestudykid
        3
    lovestudykid  
       2020-12-02 04:42:50 +08:00
    我记得直接用 Python 原生的 dict 是比较快的,用 cpp 的 map 不一定快,也可能是我哪里没弄对
    black11black
        4
    black11black  
    OP
       2020-12-02 05:21:48 +08:00
    @lovestudykid 兄,一共就四行 import,这还用再贴一下报错报了什么么。。。再说 cython 咋看报错信息啊,又没有解释器,我都是编译过程中看报错。
    black11black
        5
    black11black  
    OP
       2020-12-02 05:23:22 +08:00
    @Tony042 是这样,现在情况是有一段 python 代码,预研一下 cython 如果合适的话准备改成 cython,看了一下你说的这个项目似乎是设计用来在已经有 cpp 代码的情况下接入 py 的
    lovestudykid
        6
    lovestudykid  
       2020-12-02 05:58:09 +08:00
    @black11black 因为 code 并没有问题,编译器的报错也是报错。你没设置 language = c++?
    black11black
        7
    black11black  
    OP
       2020-12-02 06:02:22 +08:00
    @lovestudykid 感谢,是 c++声明的问题,一楼贴条里写了。另外出现了新的问题,带佬看看
    lovestudykid
        8
    lovestudykid  
       2020-12-02 06:23:01 +08:00
    @black11black 你可以试试 long long
    lovestudykid
        9
    lovestudykid  
       2020-12-02 06:24:42 +08:00
    BTW, 这是 c 和 gcc 环境的问题,跟 cython 没关系。在我这里没问题
    black11black
        10
    black11black  
    OP
       2020-12-02 06:59:00 +08:00
    @lovestudykid OK,可能是 64 位 python 用了 32 位编译器问题,大概吧。应该怎么修正呢? cython 通过 pip 装的,我不知道他用什么方法调用的编译器
    lovestudykid
        11
    lovestudykid  
       2020-12-02 07:05:44 +08:00
    @black11black 直接从你 PATH 调用的,也可以在 setup.py 制定,看 cython 文档
    xuboying
        12
    xuboying  
       2020-12-02 12:19:18 +08:00
    楼主能不能顺带测测 numba 库的效率?

    个人感觉纯计算用了 jit 技术以后应该不会有太大的差别了吧?
    black11black
        13
    black11black  
    OP
       2020-12-02 12:25:28 +08:00
    @lovestudykid 带佬指点一下怎么调,我在文档里搜索 compiler 相关的内容没看见能设置的选项,主要是这篇 https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html
    black11black
        14
    black11black  
    OP
       2020-12-02 12:36:22 +08:00
    @xuboying numba 有些黑魔法,不太喜欢用,以前测试过一些场景比纯 C 语言运行还快,不明原因。我这个环境里 jit 和 C 还是有比较大速度差距的,pypy 感觉在一些结构的实现上效率跟原生没啥区别,比如这种大字典寻址的
    black11black
        15
    black11black  
    OP
       2020-12-02 12:38:14 +08:00
    另外按照楼上老哥说的用 long long 类型以后确实程序能正常运行了。我测了一下,cython 里是有 sizeof 这个 bif 的,我测 void *p 是 8bit,而 long 是 4bit,整个人都是懵的。
    black11black
        16
    black11black  
    OP
       2020-12-02 13:16:35 +08:00
    遍地采坑啊。

    又遇到一个问题,比如 python 当中,在循环过程中新建对象是个很正常的操作,比如下面这样

    class A:

    pass

    lst = list()

    for i in range(10):

    lst.append(A())

    但是在 cython 里并不能在循环内部进行 cdef,所以现在又卡了,不会循环新建对象。
    mckelvin
        17
    mckelvin  
       2020-12-02 13:27:07 +08:00
    不要用写 Python 的思想去写 C++, 先想办法定义好胶水层的接口参数类型,一般不建议 vector 直接对应 list. 因为 list 里的内存无法直接变成 vector 可用的内存,不得不有内存拷贝发生。建议用朴素一些的数据结构,比如 double* . 数值计算大部分情况下 numpy 和 scipy 已经够用了,他们已经封装好了一些底层 C/C++实现的功能。

    数值不对这个问题大概率是溢出了。有些 64 位操作系统里 long 是 32bit 的。建议用 int64_t 这样的类型,这样明确它是 64bit 长度。

    ```
    In [16]: (1 + 1000000) / 2 * 1000000
    Out[16]: 500000000000

    In [17]: 1 << 32
    Out[17]: 4294967296
    ```
    qbqbqbqb
        18
    qbqbqbqb  
       2020-12-02 13:34:27 +08:00
    @black11black long 这个确实是个坑,64 位 Linux 里是 8 字节的,但 64 位 Windows 里是 4 字节的
    xuboying
        19
    xuboying  
       2020-12-02 17:26:16 +08:00
    @black11black #14 我以前遇到过 numba 的坑是依赖了他的类型推测导致执行不稳定,不知道这个是不是你提到的“黑魔法”,后来我去官网研究了他的形参类型声明以后,问题就完全解决了。如果这些你已经知道了,就忽略我的留言好了。btw,毕竟我没有看过汇编代码,了解程度就到这一层了。在我的图形计算的应用里效果还是相当让我满意的。
    wevsty
        20
    wevsty  
       2020-12-02 17:30:13 +08:00
    cpp 的 std::vector 是模板,模板本身只是个源码不使用的话本身并没有实例,你当然不可能用 python 直接调模板的源码。
    想要用 std::vector 的话,只能你自己用 C 封装一套函数出来。
    ipwx
        21
    ipwx  
       2020-12-02 17:36:36 +08:00
    @qbqbqbqb #18L:用 int32_t, int64_t 这种
    lovestudykid
        22
    lovestudykid  
       2020-12-02 20:30:54 +08:00   ❤️ 1
    @black11black 大概这样,CC=gcc-10 CXX=g++-10 python setup.py build_ext --inplace,也可以在 script 里设置环境变量。我用这个是因为 Mac 上默认调用 clang 有坑
    black11black
        23
    black11black  
    OP
       2020-12-03 03:09:53 +08:00
    @mckelvin
    @wevsty
    感谢大佬回复,我现在的需求很普通就是有一张从数据库导过来的二维表,这种表结构一般在 python 里是做成表套表,或者表套字典,像这样 [[],[],...] / [{},{},...] 。处理过程放到 cpp 的话确实转换开销蛮高的,所以我理解应该不能进行一些比较细粒度的 c 加速,最好是整个流程完全跑 c,这样只经过一次导入导出转换。

    如果不用 vector 的话怎么处理这种结构呢?我感觉 vector 还是挺合适的,因为 push 添加很轻松,不用考虑内存问题。我现在遇到的问题是,我不会动态向 vector 里添加对象,比如我写在 for 循环中 cdef 一个对象然后 push 进 vector 里,这种语法是不允许的
    black11black
        24
    black11black  
    OP
       2020-12-03 03:25:31 +08:00
    试了一下,似乎在子 block 里新建对象并不需要 cdef 或者 new 之类的( new 在 cython 里似乎没有这个语法),直接 object()就能创建一个对象了。不过我定义结构体后看了一下 cython 生成的分析,似乎修改结构体当中的值仍然需要进行类型校正,似乎这部分是走的 python,并不能起到加速作用。

    代码地址

    https://paste.ubuntu.com/p/s4wk9QfqPB/

    各位大佬指点一下最佳实践是什么,这么做开分析模式看的话感觉还是不太对
    black11black
        25
    black11black  
    OP
       2020-12-03 03:27:42 +08:00
    https://paste.ubuntu.com/p/cBgVXqWQJ8/

    修正,上一楼代码有小错误
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1056 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 19:14 · PVG 03:14 · LAX 12:14 · JFK 15:14
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.