Cython中处理列表/字典的惯用方法?
我的问题是:我发现用原生C++处理大数据集时,使用STL的map和vector通常比用Cython要快很多,而且占用的内存也更少。
我想这部分速度慢的原因是因为使用了Python的列表和字典,可能在Cython中有一些技巧可以使用更轻便的数据结构。例如,这个页面(http://wiki.cython.org/tutorials/numpy)展示了如何通过预先定义ND数组的大小和类型来让numpy数组在Cython中变得非常快。
我的问题是:有没有类似的方法可以用在列表和字典上,比如说大致说明一下你预计会有多少个元素或(键,值)对? 也就是说,有没有一种习惯用法可以把列表和字典转换成Cython中的(快速)数据结构?
如果没有的话,我想我只能用C++写,然后再用Cython来导入。
6 个回答
C++之所以快,不仅仅是因为它的向量和里面元素的静态声明,更重要的是通过使用模板或泛型,程序员可以指定这个向量只会包含某种特定类型的元素,比如一个包含三个元素的元组的向量。而Cython做不到这一点,这听起来并不简单——它必须在编译时强制执行某种类型检查(而Python是在运行时进行类型检查)。所以现在在Cython中,当你从一个列表中取出某个元素时,事先并不知道它是什么类型,把它放进一个有类型的变量中只会增加一次类型检查,而不会提高速度。这意味着在这方面无法绕过Python解释器,这似乎是Cython在非数值任务中最关键的缺陷。
手动解决这个问题的方法是通过cdef类来子类化Python的列表或字典(或者可能是std::vector),为特定类型的元素或键值组合创建一个新的类。这和模板生成的代码是一样的。只要在Cython代码中使用这个新类,它应该会带来一些改进。
使用数据库或数组只是解决了另一个问题,因为这里讨论的是将任意对象(但要有特定类型,最好是cdef类)放入容器中。
另外,std::map和dict不应该直接比较;std::map保持键的有序,因为它是一个平衡树,而dict解决的是不同的问题。更好的比较是dict和Google的哈希表。
在Python中做一些和C++类似的操作,通常会比较慢。虽然list
和dict
的实现都很不错,但使用Python对象会有很多额外的开销,因为Python对象比C++对象更抽象,运行时需要更多的查找。
顺便说一下,std::vector
的实现方式和list
很相似。不过,std::map
的实现方式使得在数据量变大时,很多操作的速度比dict
要慢。对于足够大的例子,dict
在速度上会超过std::map
,在查找、插入等操作上会更快。
如果你想使用std::map
和std::vector
,没有人会阻止你。不过,如果想把它们用在Python中,你需要自己进行封装。不要惊讶于这个封装过程可能会消耗掉你原本希望节省的时间。我不知道有没有工具可以自动帮你完成这个。
有一些C语言的API可以控制对象的创建,提供一些细节。你可以说“创建一个至少有这么多元素的列表”,但这并不会改善你创建和填充列表的整体复杂度。之后你尝试修改列表时,也不会有太大变化。
我的一般建议是:
如果你想要一个固定大小的数组(你提到要指定列表的大小),你可能更需要像numpy数组这样的东西。
我怀疑使用
std::vector
来替代list
,能给你带来想要的速度提升。如果你想在后台使用它,可能会在大小和空间上有一些改善(当然,我也不知道具体效果如何,你也无法确定。 ;))。dict
的工作做得非常好。我绝对不建议你基于std::map
引入一个新的通用类型在Python中使用,因为std::map
在许多重要操作上的算法复杂度更高,而且在某些实现中,dict
已经实现了一些优化,而std::map
则留给用户去处理。如果我想要一个更像
std::map
的东西,我可能会使用数据库。如果我想存储在dict
(或者list
)中的数据变得太大,无法在内存中舒适地存储,我通常会这么做。Python的标准库中有sqlite3
,还有其他主要数据库的驱动可用。
Cython现在支持模板,并且为一些标准模板库(STL)容器提供了声明。
可以查看这个链接了解更多信息:http://docs.cython.org/src/userguide/wrapping_CPlusPlus.html#standard-library
以下是他们提供的一个例子:
from libcpp.vector cimport vector
cdef vector[int] vect
cdef int i
for i in range(10):
vect.push_back(i)
for i in range(10):
print vect[i]