在 Cython 中使用 C 创建的一组列表比纯 Python 慢得多 - 为什么?



在这个例子中,我展示了两种不同的方法来使用Cython创建字符串列表。一个使用 char 指针数组(和strcpyC 函数),另一个使用简单地将元素附加到列表中。

然后,我将每个列表传递到set函数中,并发现性能截然不同。

问题- 如何使用字符指针创建列表以获得相同的性能?

在Cython中创建列表的简单函数

from libc.string cimport strcpy
def make_lists():
cdef:
char c_list[100000][3]
Py_ssize_t i
list py_list = []
for i in range(100000):
strcpy(c_list[i], 'AB')
c_list[i][2] = b''
py_list.append(b'AB')
return c_list, py_list

在这里,c_list只是一个 3 长度字符的数组。Cython 会将这个对象作为 Python 列表返回。py_list只是一个普通的Python列表。我们只用一个字节序列"AB"填充两个列表。

创建列表

c_list, py_list = make_lists()

打印出部分内容

>>> c_list[:10]
[b'AB', b'AB', b'AB', b'AB', b'AB', b'AB', b'AB', b'AB', b'AB', b'AB']

显示两个列表相等

>>> c_list == py_list
True

时间操作 - 这对我来说太疯狂了! 相差 3 倍

%timeit set(c_list)
2.85 ms ± 115 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit set(py_list)
1.02 ms ± 26 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Unicode 和纯 python

有趣的是,如果我将每个值解码为 unicode,性能差异就会消失,尽管它比原始set(py_list)慢。如果我用纯Python创建一个Unicode列表,那么我就会回到原来的性能。

c_list_unicode = [v.decode() for v in c_list]
py_list_unicode = [v.decode() for v in py_list]
py_list_py = ['AB' for _ in range(len(py_list))]
%timeit set(c_list_unicode)
1.63 ms ± 56.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit set(py_list_unicode)
1.7 ms ± 35.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit set(py_list_py)
987 µs ± 45.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

更简单的例子

def make_lists2():
cdef:
char *c_list[100000]
Py_ssize_t i
list py_list_slow = []
list py_list_fast = []
for i in range(100000):
c_list[i] = 'AB'
py_list_slow.append(c_list[i])
py_list_fast.append(b'AB')
return c_list, py_list_slow, py_list_fast

计时

c_list2, py_list_slow, py_list_fast = make_lists2()
%timeit set(c_list2)
3.01 ms ± 137 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit set(py_list_slow)
3.05 ms ± 168 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit set(py_list_fast)
1.08 ms ± 38.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

编辑

可能的解决方案

我发现该函数PyUnicode_InternFromString在 unicode Python C API 中,并且性能与常规 python 列表相当。这"实习"了字符串 - 不确定这意味着什么

您的c_list是具有相同内容的 100000 个不同字节串的列表。Cython必须将每个char[3]分别转换为字节串,并且无需进行任何对象重复数据删除。

您的py_list是 100000 次相同字节串对象的列表。每个py_list.append(b'AB')都将相同的对象附加到py_list;如果没有通过 C 数组的行程,Cython 永远不需要复制字节串。

set(c_list)set(py_list)慢,因为set(c_list)必须实际执行字符串比较,而set(py_list)可以通过对象身份检查来跳过它。

最新更新