如题,最近有一个大量用到 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']
这类字符串寻址来搜寻具体对象,对象内容不复杂,可以映射为结构体,但是这个字典该怎么搞?
谢谢大家
贴个条,载入的问题通过在文件头声明
# 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,神秘
上一条代码有错,重打
# 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
1
Tony042 2020-12-02 03:44:27 +08:00
考虑下 pybind11 来进行 python 和 C++交互?
|
2
lovestudykid 2020-12-02 04:40:20 +08:00
应该是 cimport cython 吧,报错也不说什么错这怎么 debug
|
3
lovestudykid 2020-12-02 04:42:50 +08:00
我记得直接用 Python 原生的 dict 是比较快的,用 cpp 的 map 不一定快,也可能是我哪里没弄对
|
4
black11black OP @lovestudykid 兄,一共就四行 import,这还用再贴一下报错报了什么么。。。再说 cython 咋看报错信息啊,又没有解释器,我都是编译过程中看报错。
|
5
black11black OP @Tony042 是这样,现在情况是有一段 python 代码,预研一下 cython 如果合适的话准备改成 cython,看了一下你说的这个项目似乎是设计用来在已经有 cpp 代码的情况下接入 py 的
|
6
lovestudykid 2020-12-02 05:58:09 +08:00
@black11black 因为 code 并没有问题,编译器的报错也是报错。你没设置 language = c++?
|
7
black11black OP @lovestudykid 感谢,是 c++声明的问题,一楼贴条里写了。另外出现了新的问题,带佬看看
|
8
lovestudykid 2020-12-02 06:23:01 +08:00
@black11black 你可以试试 long long
|
9
lovestudykid 2020-12-02 06:24:42 +08:00
BTW, 这是 c 和 gcc 环境的问题,跟 cython 没关系。在我这里没问题
|
10
black11black OP @lovestudykid OK,可能是 64 位 python 用了 32 位编译器问题,大概吧。应该怎么修正呢? cython 通过 pip 装的,我不知道他用什么方法调用的编译器
|
11
lovestudykid 2020-12-02 07:05:44 +08:00
@black11black 直接从你 PATH 调用的,也可以在 setup.py 制定,看 cython 文档
|
12
xuboying 2020-12-02 12:19:18 +08:00
楼主能不能顺带测测 numba 库的效率?
个人感觉纯计算用了 jit 技术以后应该不会有太大的差别了吧? |
13
black11black OP @lovestudykid 带佬指点一下怎么调,我在文档里搜索 compiler 相关的内容没看见能设置的选项,主要是这篇 https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html
|
14
black11black OP @xuboying numba 有些黑魔法,不太喜欢用,以前测试过一些场景比纯 C 语言运行还快,不明原因。我这个环境里 jit 和 C 还是有比较大速度差距的,pypy 感觉在一些结构的实现上效率跟原生没啥区别,比如这种大字典寻址的
|
15
black11black OP 另外按照楼上老哥说的用 long long 类型以后确实程序能正常运行了。我测了一下,cython 里是有 sizeof 这个 bif 的,我测 void *p 是 8bit,而 long 是 4bit,整个人都是懵的。
|
16
black11black OP 遍地采坑啊。
又遇到一个问题,比如 python 当中,在循环过程中新建对象是个很正常的操作,比如下面这样 class A: pass lst = list() for i in range(10): lst.append(A()) 但是在 cython 里并不能在循环内部进行 cdef,所以现在又卡了,不会循环新建对象。 |
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 ``` |
18
qbqbqbqb 2020-12-02 13:34:27 +08:00
@black11black long 这个确实是个坑,64 位 Linux 里是 8 字节的,但 64 位 Windows 里是 4 字节的
|
19
xuboying 2020-12-02 17:26:16 +08:00
@black11black #14 我以前遇到过 numba 的坑是依赖了他的类型推测导致执行不稳定,不知道这个是不是你提到的“黑魔法”,后来我去官网研究了他的形参类型声明以后,问题就完全解决了。如果这些你已经知道了,就忽略我的留言好了。btw,毕竟我没有看过汇编代码,了解程度就到这一层了。在我的图形计算的应用里效果还是相当让我满意的。
|
20
wevsty 2020-12-02 17:30:13 +08:00
cpp 的 std::vector 是模板,模板本身只是个源码不使用的话本身并没有实例,你当然不可能用 python 直接调模板的源码。
想要用 std::vector 的话,只能你自己用 C 封装一套函数出来。 |
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 有坑
|
23
black11black OP @mckelvin
@wevsty 感谢大佬回复,我现在的需求很普通就是有一张从数据库导过来的二维表,这种表结构一般在 python 里是做成表套表,或者表套字典,像这样 [[],[],...] / [{},{},...] 。处理过程放到 cpp 的话确实转换开销蛮高的,所以我理解应该不能进行一些比较细粒度的 c 加速,最好是整个流程完全跑 c,这样只经过一次导入导出转换。 如果不用 vector 的话怎么处理这种结构呢?我感觉 vector 还是挺合适的,因为 push 添加很轻松,不用考虑内存问题。我现在遇到的问题是,我不会动态向 vector 里添加对象,比如我写在 for 循环中 cdef 一个对象然后 push 进 vector 里,这种语法是不允许的 |
24
black11black OP 试了一下,似乎在子 block 里新建对象并不需要 cdef 或者 new 之类的( new 在 cython 里似乎没有这个语法),直接 object()就能创建一个对象了。不过我定义结构体后看了一下 cython 生成的分析,似乎修改结构体当中的值仍然需要进行类型校正,似乎这部分是走的 python,并不能起到加速作用。
代码地址 https://paste.ubuntu.com/p/s4wk9QfqPB/ 各位大佬指点一下最佳实践是什么,这么做开分析模式看的话感觉还是不太对 |
25
black11black OP |