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

在Cython中使用C创建一组列表要比纯Python慢得多——为什么?,python,cython,Python,Cython,在本例中,我展示了使用Cython创建字符串列表的两种不同方法。一个使用字符指针数组(以及strcpyC函数),另一个只需将元素添加到列表中即可 然后,我将这些列表传递到set函数中,发现性能有很大的不同 问题-如何使用字符指针创建具有同等性能的列表 在Cython中创建列表的简单函数 from libc.string cimport strcpy def make_lists(): cdef: char c_list[100000][3] Py_ssi

在本例中,我展示了使用Cython创建字符串列表的两种不同方法。一个使用字符指针数组(以及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'\0'
        py_list.append(b'AB')

    return c_list, py_list
这里,
c_list
只是一个3长度字符的数组。Cython将以Python列表的形式返回此对象
py_列表
只是一个普通的Python列表。我们用一个字节序列“AB”来填充这两个列表

创建列表 打印出一些内容 显示两个列表相等 时间运作-这对我来说太疯狂了!3倍差 Unicode和纯python 有趣的是,如果我将每个值解码为unicode,性能差异就会消失,尽管它比原始的
集(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)
更简单的例子 时间安排

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中找到了这个函数,AM的性能与常规Python列表一样。这个“实习生”字符串-不确定这是什么意思您的
c_列表是一个包含100000个具有相同内容的不同bytestring的列表。Cython必须将每个
char[3]
分别转换为一个bytestring,而且它不需要进行任何对象重复数据消除

您的
py_list
是同一个bytestring对象100000次的列表。每个
py_list.append(b'AB')
将相同的对象追加到
py_list
;如果不经过C数组,Cython永远不需要复制bytestring


set(c\u-list)
set(py\u-list)
慢,因为
set(c\u-list)
必须实际执行字符串比较,而
set(py\u-list)
可以通过对象标识检查跳过该操作。

这与
cython
没有什么关系。您正在比较从列表中创建一个集合与一般缓冲区(来自C world)的性能。事实上,我预计性能会下降。在unicode示例中,您正在将所有内容转换为python列表,因此性能差距消失了。但是,这两个对象都是python列表,因为我使用的是纯python。我猜它们的基本内存分配是完全不同的。此外,在转换为unicode后,列表的速度仍然是纯python unicode列表的2倍。可能是。从python列表到python集的转换应该比外部创建的列表更优化。在后一种情况下也可能会出现一些安全防护措施。这听起来几乎可以肯定是因为
c_list
是不同bytestring的列表,而
py_list
是对同一bytestring的100000个引用的列表。我没有安装Cython,因此无法确认。另外,
strcpy
是否为您复制空终止符?谢谢您的解释。你能用函数
PyUnicode\u InternFromString
和新的计时来显示我的“更简单的例子”吗,以表明它们现在是相同的。
>>> 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)
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)