Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/296.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 使用cython比struct.pack速度更快_Python_Struct_Cython_Pack - Fatal编程技术网

Python 使用cython比struct.pack速度更快

Python 使用cython比struct.pack速度更快,python,struct,cython,pack,Python,Struct,Cython,Pack,我试图做得比struct.pack更好 以打包整数的特定案例为例,通过对的回答,我可以在pack_ints.pyx中打包一个整数列表: # cython: language_level=3, boundscheck=False import cython @cython.boundscheck(False) @cython.wraparound(False) def pack_ints(int_col): int_buf = bytearray(4*len(int_col))

我试图做得比struct.pack更好

以打包整数的特定案例为例,通过对的回答,我可以在
pack_ints.pyx
中打包一个整数列表:

# cython: language_level=3, boundscheck=False
import cython

@cython.boundscheck(False)
@cython.wraparound(False)
def pack_ints(int_col):

    int_buf = bytearray(4*len(int_col))
    cdef int[::1] buf_view = memoryview(int_buf).cast('i')

    idx: int = 0
    for idx in range(len(int_col)):
        buf_view[idx] = int_col[idx]


    return int_buf
使用ipython中的此测试代码:

from struct import pack 
import pyximport; pyximport.install(language_level=3) 
import pack_ints 

amount = 10**7 
ints = list(range(amount)) 

res1 = pack(f'{amount}i', *ints) 
res2 = pack_ints.pack_ints(ints) 
assert(res1 == res2) 

%timeit pack(f'{amount}i', *ints)  
%timeit pack_ints.pack_ints(ints)      
我得到:

304 ms ± 2.18 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
212 ms ± 6.54 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
我尝试将
int\u buf
作为
数组('b')
键入,但没有看到任何改进


是否有其他方法对此进行改进,或者以不同的方式使用cython,以使此操作更快?

当我从原始问题运行代码时,我的速度提高了约5倍

当我在这里运行代码时,我看到您报告的结果,以及编译阶段的一个重要警告,我认为您忽略了这个警告:

警告:应键入pack_ints.pyx:13:17:索引以提高访问效率

我不确定它为什么没有正确地拾取类型,但要修复它,您应该将
I
的定义更改回我最初编写的代码:

cdef int i
# not "i: int"


希望其他人会来尝试更聪明的方法,因为这显然有点荒谬。

这个答案试图给出一个估计,一个并行版本可以产生多大的速度。但是,由于此任务是内存带宽受限的(Python integer对象至少占用32个字节,并且可以分散在内存中,因此会有许多缓存未命中),因此我们不应该期望太多

第一个问题是,如何处理错误(元素不是整数或值太大)。我将遵循以下策略/简化:当对象

  • 不是整数
  • 是负整数
  • 或整数>=2^30
它将被转换成一个特殊的数字(
-1
),表示出了问题。仅允许非负整数
2**30-1
int到_int(PyObject*vv){
如果(PyLong_检查(vv)){
PyLongObject*v=(PyLongObject*)vv;
Py_ssize_t i=Py_尺寸(v);
如果(i==0){
返回0;
}
如果(i==1){//一个数字足够小
返回v->ob_位[0];
}
//负(i1)
返回-1;
}
返回-1;
}
现在给定一个列表,我们可以将其转换为一个
int
-缓冲区,并使用以下使用omp的C函数:

void convert_list(PyListObject *lst, int *output){
    Py_ssize_t n = Py_SIZE(lst);
    PyObject **data = lst->ob_item;
    #pragma omp parallel for
    for(Py_ssize_t i=0; i<n; ++i){
        output[i] = to_int(data[i]);
    }
}
一个重要的细节是:
convert\u list
不能是nogil(因为它不是)!Omp线程和Python线程(受GIL影响)完全不同

在使用对象时,可以(但没有必要)为omp操作释放GIL,因为这些对象通过缓冲协议被锁定,不能从不同的Python线程中更改。
list
没有这样的锁定机制,因此,如果GIL被释放,列表可能会在另一个线程中被更改,我们所有的指针都可能失效

现在来看看时间安排(列表略大):

金额=5*10**7
整数=列表(范围(金额))
%timeit包(f'{amount}i',*整数)
#每个回路1.51 s±38.9 ms(7次运行的平均值±标准偏差,每个回路1次)
%timeit pack\u ints\u DavidW(ints)
#每个回路284 ms±3.1 ms(7次运行的平均值±标准偏差,每个回路1次)
%timeit pack_ints_ead(ints)
#每个回路177 ms±11.8 ms(7次运行的平均值±标准偏差,每个10个回路)
顺便说一句,关闭
pack\u ints\u ead的并行化会导致运行时间为209ms

因此,考虑到约33%的适度改善,我会选择更稳健的DavidW解决方案


以下是一种稍有不同的方法来表示错误值的实现:

  • 不是整数对象会导致
    -2147483648
    (即
    0x8000000
    )-32位int可以存储的最小负值
  • 整数
    =2147483647
    (即
    =0x7fffffff
    )将映射到/存储为
    2147483647
    ——32位整数可以存储的最大正数
  • 整数
    >1)==0){
    int add=(v->ob_位[1]&1)ob_位[0]+add);
    }
    返回符号*0x7fffffff;
    }
    返回0x8000000;
    }
    
    我怀疑你能跑得这么快。为什么您认为有可能更快地执行操作?例如,我猜如果有人通过一些C代码获得指向列表对象的指针,他可以打开线程并并行工作,速度会随着内核数量的增加而加快。我不知道如何在cython中实现这一点,在这个函数上无法访问
    @nogil
    。我认为您需要gil将python对象转换为c-int(例如,它可能引发异常)。我建议您做一件更有用的事情,就是修复输入数据。您是否可以直接生成打包数据,而不是浪费时间生成一个大型Python对象列表,然后对其进行转换?。。。还有。您需要调整实现,使其不使用GIL(即不能引发异常)。谢谢!这段C代码是由cython编译的吗?此外,如果我使用
    tuple()
    将列表转换为一个元组,那么我可以使用
    nogil
    ?@Jay将C代码合并到Cython同时生成和编译的C文件中。转换为
    tuple
    nogil
    没有影响,但是
    nogil
    在这里并不重要-循环会像您希望的那样并行运行(没有一个线程需要GIL,因为它们只是读取而不是更改,但是其中一个线程应该有GIL,这样就没有其他线程可以并行运行并更改东西了-我同意这有点混乱…@Jay我使用它来包含C代码逐字记录。您可以对实现缓冲协议的对象使用nogil()元组和列表都没有。但是,正如DavidW指出的那样,您不需要这样做-计算已经并行进行。我相信在检查注释时这行是白色的,所以我不关心警告。如果cdef跳到X5@Jay改变
    void convert_list(PyListObject *lst, int *output){
        Py_ssize_t n = Py_SIZE(lst);
        PyObject **data = lst->ob_item;
        #pragma omp parallel for
        for(Py_ssize_t i=0; i<n; ++i){
            output[i] = to_int(data[i]);
        }
    }