使用 cython 比 struct.pack 更快

2024-02-25

我正在努力做得比struct.pack.

以包装整数的具体情况为例,通过答案这个问题 https://stackoverflow.com/questions/58960014/how-to-loop-over-a-list-in-cython-pure-mode,我有以下内容来打包整数列表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)      

I get:

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_buf as an array('b'),但没有看到改善。

有没有其他方法可以改进这一点,或者以不同的方式使用 cython ,以使此操作更快?


这个答案试图给出一个估计,并行版本可以产生多少加速。然而,因为这个任务是内存带宽限制的(Python 整数对象至少需要 32 个字节,并且可以分散在内存中,所以会有很多缓存未命中),我们不应该期望太多。

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

  • 不是整数,
  • 是负整数,
  • 或整数 >=2^30

它将被转换为一个特殊的数字(-1)这表明出现了问题。仅允许非负整数<2^30让我的生活更轻松,因为我必须重新实现PyLong_AsLongAndOverflow https://github.com/python/cpython/blob/5428f48b6308c7fd71636077f2ebc307c9a53d03/Objects/longobject.c#L486没有引发错误并且检测溢出通常很麻烦(但是,请参阅答案末尾的版本以获取更复杂的方法)。

Python整型对象的内存布局可以找到here https://github.com/python/cpython/blob/e42b705188271da108de42b55d9344642170aa2b/Include/longintrepr.h#L85:

struct _longobject {
    PyObject_VAR_HEAD
    digit ob_digit[1];
};

Member ob_size/macro Py_SIZE https://docs.python.org/3/c-api/structures.html#c.PyObject_VAR_HEAD告诉我们在整数的表示中使用了多少个 30 位数字(ob_size负整数为负)。

因此,我的简单规则可以转换为以下 C 代码(我使用 C 而不是 Cython,因为它是使用 Python C-API 的更简单/更自然的方式):

#include <Python.h>

// returns -1 if vv is not an integer,
//            negative, or > 2**30-1
int to_int(PyObject *vv){ 
   if (PyLong_Check(vv)) {
       PyLongObject * v = (PyLongObject *)vv;
       Py_ssize_t i = Py_SIZE(v);
       if(i==0){
           return 0;
       }
       if(i==1){//small enought for a digit
           return v->ob_digit[0];
       }
       //negative (i<0) or too big (i>1)
       return -1;
   }
   return -1;
}

现在给定一个列表,我们可以将其转换为int-buffer 与以下使用 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]);
    }
}

没什么好说的——PyListObject-API https://github.com/python/cpython/blob/cd7db76a636c218b2d81d3526eb435cfae61f212/Include/listobject.h#L40用于并行访问列表的元素。这是可以做到的,因为没有裁判计数/竞赛条件to_int-功能。

现在,将它们与 Cython 捆绑在一起:

%%cython -c=-fopenmp --link-args=-fopenmp
import cython

cdef extern from *:
    """
    #include <Python.h>

    int to_int(PyObject *vv){ 
       ... code
    }

    void convert_list(PyListObject *lst, int *output){
        ... code
    }
    """
    void convert_list(list lst, int *output)

@cython.boundscheck(False)
@cython.wraparound(False)
def pack_ints_ead(list int_col):
    cdef char[::1] int_buf = bytearray(4*len(int_col))
    convert_list(int_col, <int*>(&int_buf[0]))
    return int_buf.base

一个重要的细节是:convert_list 一定不能是诺吉尔(因为它不是)! Omp 线程和 Python 线程(受 GIL 影响)是完全不同的东西。

在使用对象时,可以(但不是必须)为 omp 操作释放 GIL缓冲协议 https://docs.python.org/3/c-api/buffer.html- 因为这些对象通过缓冲区协议被锁定,并且不能从不同的 Python 线程进行更改。 Alist没有这样的锁定机制,因此,如果 GIL 被释放,列表可能会在另一个线程中更改,并且我们所有的指针都可能会失效。

现在是时间安排(列表稍大一些):

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


%timeit pack(f'{amount}i', *ints)  
# 1.51 s ± 38.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit pack_ints_DavidW(ints) 
# 284 ms ± 3.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit pack_ints_ead(ints) 
# 177 ms ± 11.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

顺便说一句关闭并行化pack_ints_ead导致运行时间为 209 毫秒。

因此,考虑到大约的适度改进。 33%,我会选择更强大的 DavidW 解决方案。


以下是用稍微不同的方式发出错误值的实现:

  • 不是整数对象会导致-2147483648(i.e. 0x80000000) - 32 位 int 可以存储的最小负值。
  • 整数>=2147483647 (i.e. >=0x7fffffff)将被映射到/存储为2147483647- 32 位 int 可以存储的最大正数。
  • 整数<=-2147483647 (i.e. <=0x80000001)将被映射到/存储为-2147483647
  • 所有其他整数都映射到它们的正确值。

主要优点是,它可以正确地处理更大范围的整数值。该算法产生与第一个简单版本几乎相同的运行时间(可能慢 2-3%):

int to_int(PyObject *vv){ 
   if (PyLong_Check(vv)) {
       PyLongObject * v = (PyLongObject *)vv;
       Py_ssize_t i = Py_SIZE(v);
       int sign = i<0 ? -1 : 1;
       i = abs(i);
       if(i==0){
           return 0;
       }
       if(i==1){//small enought for a digit
           return sign*v->ob_digit[0];
       }
       if(i==2 && (v->ob_digit[1]>>1)==0){
           int add = (v->ob_digit[1]&1) << 30;
           return sign*(v->ob_digit[0]+add);
       }
       return sign * 0x7fffffff;
   }
   return 0x80000000;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 cython 比 struct.pack 更快 的相关文章

  • Lighttpd 和 cgi python

    我正在尝试通过 lighttpd 执行一些 python 脚本 但是当我尝试运行它时 我只得到一个要求我下载的空白文件 lighttpd conf server modules mod access mod alias mod access
  • python:查找围绕某个 GPS 位置的圆的 GPS 坐标的优雅方法

    我有一组以十进制表示的 GPS 坐标 并且我正在寻找一种方法来查找每个位置周围半径可变的圆中的坐标 这是一个例子 http green and energy com downloads test circle html我需要什么 这是一个圆
  • 使用 python requests 模块时出现 HTTP 503 错误

    我正在尝试发出 HTTP 请求 但当前可以从 Firefox 浏览器访问的网站响应 503 错误 代码本身非常简单 在网上搜索一番后我添加了user Agent请求参数 但也没有帮助 有人能解释一下如何消除这个 503 错误吗 顺便说一句
  • Django 的内联管理:一个“预填充”字段

    我正在开发我的第一个 Django 项目 我希望用户能够在管理中创建自定义表单 并向其中添加字段当他或她需要它们时 为此 我在我的项目中添加了一个可重用的应用程序 可在 github 上找到 https github com stephen
  • 与区域指示符字符类匹配的 python 正则表达式

    我在 Mac 上使用 python 2 7 10 表情符号中的标志由一对表示区域指示符号 https en wikipedia org wiki Regional Indicator Symbol 我想编写一个 python 正则表达式来在
  • 将html数据解析成python列表进行操作

    我正在尝试读取 html 网站并提取其数据 例如 我想查看公司过去 5 年的 EPS 每股收益 基本上 我可以读入它 并且可以使用 BeautifulSoup 或 html2text 创建一个巨大的文本块 然后我想搜索该文件 我一直在使用
  • Pandas/Google BigQuery:架构不匹配导致上传失败

    我的谷歌表中的架构如下所示 price datetime DATETIME symbol STRING bid open FLOAT bid high FLOAT bid low FLOAT bid close FLOAT ask open
  • Python getstatusoutput 替换不返回完整输出

    我发现了这个很棒的替代品getstatusoutput Python 2 中的函数在 Unix 和 Windows 上同样有效 不过我觉得这个方法有问题output被构建 它只返回输出的最后一行 但我不明白为什么 任何帮助都是极好的 def
  • 跟踪 pypi 依赖项 - 谁在使用我的包

    无论如何 是否可以通过 pip 或 PyPi 来识别哪些项目 在 Pypi 上发布 可能正在使用我的包 也在 PyPi 上发布 我想确定每个包的用户群以及可能尝试积极与他们互动 预先感谢您的任何答案 即使我想做的事情是不可能的 这实际上是不
  • 使用字典映射数据帧索引

    为什么不df index map dict 工作就像df column name map dict 这是尝试使用index map的一个小例子 import pandas as pd df pd DataFrame one A 10 B 2
  • 立体太阳图 matplotlib 极坐标图 python

    我正在尝试创建一个与以下类似的简单的立体太阳路径图 http wiki naturalfrequent com wiki Sun Path Diagram http wiki naturalfrequency com wiki Sun Pa
  • 为什么 PyYAML 花费这么多时间来解析 YAML 文件?

    我正在解析一个大约 6500 行的 YAML 文件 格式如下 foo1 bar1 blah name john age 123 metadata whatever1 whatever whatever2 whatever stuff thi
  • 如何通过 TLS 1.2 运行 django runserver

    我正在本地 Mac OS X 机器上测试 Stripe 订单 我正在实现这段代码 stripe api key settings STRIPE SECRET order stripe Order create currency usd em
  • 如何使用 pybrain 黑盒优化训练神经网络来处理监督数据集?

    我玩了一下 pybrain 了解如何生成具有自定义架构的神经网络 并使用反向传播算法将它们训练为监督数据集 然而 我对优化算法以及任务 学习代理和环境的概念感到困惑 例如 我将如何实现一个神经网络 例如 1 以使用 pybrain 遗传算法
  • 如何在 Windows 命令行中使用参数运行 Python 脚本

    这是我的蟒蛇hello py script def hello a b print hello and that s your sum sum a b print sum import sys if name main hello sys
  • 在本地网络上运行 Bokeh 服务器

    我有一个简单的 Bokeh 应用程序 名为app py如下 contents of app py from bokeh client import push session from bokeh embed import server do
  • Python ImportError:无法导入名称 __init__.py

    我收到此错误 ImportError cannot import name life table from cdc life tables C Users tony OneDrive Documents Retirement retirem
  • 模拟pytest中的异常终止

    我的多线程应用程序遇到了一个错误 主线程的任何异常终止 例如 未捕获的异常或某些信号 都会导致其他线程之一死锁 并阻止进程干净退出 我解决了这个问题 但我想添加一个测试来防止回归 但是 我不知道如何在 pytest 中模拟异常终止 如果我只
  • 如何计算Python中字典中最常见的前10个值

    我对 python 和一般编程都很陌生 所以请友善 我正在尝试分析包含音乐信息的 csv 文件并返回最常听的前 n 个乐队 从下面的代码中 每听一首歌曲都是一个列表中的字典条目 格式如下 album Exile on Main Street
  • Pandas 每周计算重复值

    我有一个Dataframe包含按周分组的日期和 ID df date id 2022 02 07 1 3 5 4 2022 02 14 2 1 3 2022 02 21 9 10 1 2022 05 16 我想计算每周有多少 id 与上周重

随机推荐