在这个例子中,我展示了两种使用Cython创建字符串列表的方法。一个使用char指针数组(和strcpy
C函数),另一个使用简单地将元素附加到列表中。
然后我将这些列表中的每一个传递到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'\0'
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
%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,性能差异就会消失,尽管它比原始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)
修改
我在unicode Python C API中找到了函数PyUnicode_InternFromString
,并获得了与常规python列表相同的性能。这个'实习'字符串 - 不知道这意味着什么
答案 0 :(得分:3)
您的c_list
是包含相同内容的100000个不同字节串的列表。 Cython必须分别将每个char[3]
转换为字节字符串,并且它不需要做任何对象重复数据删除。
您的py_list
是相同bytestring对象的列表100000次。每个py_list.append(b'AB')
都将同一个对象追加到py_list
;如果没有通过C数组的行程,Cython永远不需要复制bytestring。
set(c_list)
比set(py_list)
慢,因为set(c_list)
必须实际执行字符串比较,而set(py_list)
会通过对象标识检查跳过它。