python - 为什么内置 array.fromlist() 比 cython 代码慢?

标签 python performance cython

通常,当将 Python 和 C 代码粘合在一起时,需要将 Python 列表转换为连续的内存,例如一个 array.array。这个转换步骤成为瓶颈也很常见,所以我发现自己用 Cython 做一些愚蠢的事情,因为它比内置的 Python 解决方案更快。

例如,将 Python 列表 lst 转换为 int32 连续内存我知道两种可能性:

a=array.array('i', lst)

a=array.array('i'); 
a.fromlist(lst)

但是它们都比下面的 cython 版本慢:

%%cython
import array
from cpython cimport array
def array_from_list_iter(lst):
    cdef Py_ssize_t n=len(lst)
    cdef array.array res=array.array('i')
    cdef int cnt=0
    array.resize(res, n)  #preallocate memory
    for i in lst:
       res.data.as_ints[cnt]=i
       cnt+=1
    return res

我的时间显示(Linux、Python3.6,但 Windows 和/或 Python2.7 的结果非常相似),cython 解决方案快了大约 6 倍:

Size       new_array   from_list  cython_iter    factor
1             284ns    347ns        176ns           1.6
10            599ns    621ns        209ns           2.9
10**2         3.7µs    3.5µs        578ns           6.1
10**3        38.5µs    32µs         4.3µs           7.4
10**4         343µs    316µs       40.4µs           7.8
10**5         3.5ms    3.4ms        481µs           7.1
10**6        34.1ms    31.5ms       5.0ms           6.3
10**7         353ms    316ms       53.3ms           5.9

以我对 CPython 的有限理解,我会说 from_list-解决方案使用这个 build-in function :

static PyObject *
array_array_fromlist(arrayobject *self, PyObject *list)
{
    Py_ssize_t n;

    if (!PyList_Check(list)) {
        PyErr_SetString(PyExc_TypeError, "arg must be list");
        return NULL;
    }
    n = PyList_Size(list);
    if (n > 0) {
        Py_ssize_t i, old_size;
        old_size = Py_SIZE(self);
        if (array_resize(self, old_size + n) == -1)
            return NULL;
        for (i = 0; i < n; i++) {
            PyObject *v = PyList_GetItem(list, i);
            if ((*self->ob_descr->setitem)(self,
                            Py_SIZE(self) - n + i, v) != 0) {
                array_resize(self, old_size);
                return NULL;
            }
        }
    }
    Py_RETURN_NONE;
}

a=array.array('i', lst) grows dynamically并且需要重新分配,所以这可以解释一些减速(但正如测量显示的那样,并没有太多!),但是 array_fromlist 预分配了所需的内存 - 它基本上与 Cython 的算法完全相同-代码。

那么问题来了:为什么这个 Python 代码比 Cython 代码慢 6 倍?我错过了什么?


下面是测量时间的代码:

import array
import numpy as np
for n in [1, 10,10**2, 10**3, 10**4, 10**5, 10**6, 10**7]:
    print ("N=",n)
    lst=list(range(n))
    print("python:")
    %timeit array.array('i', lst)
    print("python, from list:")
    %timeit a=array.array('i'); a.fromlist(lst)
    print("numpy:")
    %timeit np.array(lst, dtype=np.int32)
    print("cython_iter:")
    %timeit array_from_list_iter(lst)

numpy 解决方案比 python 版本慢约 2 倍。

最佳答案

最大的区别似乎是实际的 int 拆箱。 CPython 数组实现使用 PyArg_Parse而 cython 正在调用 PyLong_AsLong - 至少我认为,通过几层宏。

%%cython -a
from cpython cimport PyArg_Parse
def arg_parse(obj):
    cdef int i
    for _ in range(100000):
        PyArg_Parse(obj, "i;array item must be integer", &i)
    return i

def cython_parse(obj):
    cdef int i
    for _ in range(100000):
        i = obj
    return i

%timeit arg_parse(1)
# 2.52 ms ± 67.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit cython_parse(1)
# 299 µs ± 1.86 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

关于python - 为什么内置 array.fromlist() 比 cython 代码慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48784935/

相关文章:

python - 从 Django View 发送 JSON 数据

python - 在文件中创建工作数据结构

python - `cimport numpy` 使用 Cython 引发错误

python - 如何在 Cython 包装器的 setup.py 中添加 pkg-config

python - Cython 导入错误 : No module named parallel

python - 我如何在 Django 中设置我的博客文章的 HTML 格式?

Python 代码不读取 Travis CI 环境变量

performance - 有人可以澄清摊销常数时间(加倍数组向量)吗?

sql-server - 系统升级后虚拟机上的 SQL Server 性能不佳

java - x*x 与 Math.pow(x,2) java 性能对比