我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python使用Zero-Copy和Bu

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python使用Zero-Copy和Bu

无论你程序是做什么的,它经常都需要处理大量的数据。这些数据大部分表现形式为strings(字符串)。然而,当你对字符串大批量的拷贝,切片和修改操作时是相当低效的。为什么?

让我们假设一个读取二进制数据的大文件示例,然后将部分数据拷贝到另外一个文件。要展示该程序所使用的内存,我们使用memory_profiler,一个强大的Python包,让我们可以一行一行观察程序所使用的内存。

@profile
def read_random():
    with open("/dev/urandom", "rb") as source:
        content = source.read(1024 * 10000)
        content_to_write = content[1024:]
    print(f"content length: {len(content)}, content to write length {len(content_to_write)}")
    with open("/dev/null", "wb") as target:
        target.write(content_to_write)


if __name__ == "__main__":
    read_random()

使用memory_profiler模块来执行以上程序,输出如下:

$ python -m memory_profiler example.py 
content length: 10240000, content to write length 10238976
Filename: example.py

Line #    Mem usage    Increment   Line Contents
================================================
     1   14.320 MiB   14.320 MiB   @profile
     2                             def read_random():
     3   14.320 MiB    0.000 MiB       with open("/dev/urandom", "rb") as source:
     4   24.117 MiB    9.797 MiB           content = source.read(1024 * 10000)
     5   33.914 MiB    9.797 MiB           content_to_write = content[1024:]
     6   33.914 MiB    0.000 MiB       print(f"content length: {len(content)}, content to write length {len(content_to_write)}")
     7   33.914 MiB    0.000 MiB       with open("/dev/null", "wb") as target:
     8   33.914 MiB    0.000 MiB           target.write(content_to_write)

我们通过source.read/dev/unrandom加载了10 MB数据。Python需要大概需要分配10 MB内存来以字符串存储这个数据。之后的content[1024:]指令越过开头的一个单位的KB数据进行数据拷贝,也分配了大概10 MB。

这里有趣的是在哪里呢,也就是构建content_to_write时10 MB的程序内存增长。切片操作拷贝了除了开头的一个单位的KB其他所有的数据到一个新的字符串对象。

如果处理类似大量的字节数组对象操作那是简直就是灾难。如果你之前写过C语言,在使用memcpy()需要注意点是:在内存使用以及总体性能来说,复制内存很慢。

然而,作为C程序员的你,知道字符串其实就是由字符数组构成,你不非得通过拷贝也能只处理部分字符,通过使用基本的指针运算——只需要确保整个字符串是连续的内存区域。

在Python同样提供了buffer protocol实现。buffer protocol定义在PEP 3118,描述了使用C语言API实现各种类型的支持,例如字符串。

当一个对象实现了该协议,你就可以使用memoryview类构造一个memoryview对象引用原始内存对象。

>>> s = b"abcdefgh"
>>> view = memoryview(s)
>>> view[1]
98
>>> limited = view[1:3]
>>> limited
<memory at 0x7f6ff2df1108>
>>> bytes(view[1:3])
b'bc'

注意:98是字符b的ACSII码

在上面的例子中,在使用memoryview对象的切片操作,同样返回一个memoryview对象。意味着它并没有拷贝任何数据,而是通过引用部分数据实现的。

下面图示解释发生了什么:

alt

因此,我们可以将之前的程序改造得更加高效。我们需要使用memoryview对象来引用数据,而不是开辟一个新的字符串。

@profile
def read_random():
    with open("/dev/urandom", "rb") as source:
        content = source.read(1024 * 10000)
        content_to_write = memoryview(content)[1024:]
    print(f"content length: {len(content)}, content to write length {len(content_to_write)}")
    with open("/dev/null", "wb") as target:
        target.write(content_to_write)


if __name__ == "__main__":
    read_random()

我们再一次使用memory profiler执行上面程序:

$ python -m memory_profiler example.py 
content length: 10240000, content to write length 10238976
Filename: example.py

Line #    Mem usage    Increment   Line Contents
================================================
     1   14.219 MiB   14.219 MiB   @profile
     2                             def read_random():
     3   14.219 MiB    0.000 MiB       with open("/dev/urandom", "rb") as source:
     4   24.016 MiB    9.797 MiB           content = source.read(1024 * 10000)
     5   24.016 MiB    0.000 MiB           content_to_write = memoryview(content)[1024:]
     6   24.016 MiB    0.000 MiB       print(f"content length: {len(content)}, content to write length {len(content_to_write)}")
     7   24.016 MiB    0.000 MiB       with open("/dev/null", "wb") as target:
     8   24.016 MiB    0.000 MiB           target.write(content_to_write)

在该程序中,source.read仍然分配了10 MB内存来读取文件内容。然而,使用memoryview来引用部分内容时,并没有额外在分配内存。

相比之前的版本,这里节省了大概50%的内存开销。

该技巧,在处理sockets通信的时候极其有用。当通过socket发送数据时,所有的数据可能并没有在一次调用就发送。

import socket
s = socket.socket(…)
s.connect(…)
# Build a bytes object with more than 100 millions times the letter `a`
data = b"a" * (1024 * 100000)
while data:
    sent = s.send(data)
    # Remove the first `sent` bytes sent
    data = data[sent:] <2>

使用如下实现,程序一次次拷贝直到所有的数据发出。通过使用memoryview,可以实现zero-copy(零拷贝)方式来完成该工作,具有更高的性能:

import socket
s = socket.socket(…)
s.connect(…)
# Build a bytes object with more than 100 millions times the letter `a`
data = b"a" * (1024 * 100000)
mv = memoryview(data)
while mv:
    sent = s.send(mv)
    # Build a new memoryview object pointing to the data which remains to be sent
    mv = mv[sent:]

在这里就不会发生任何拷贝,也不会在给data分配了100 MB内存之后再分配多余的内存来进行多次发送了。

目前,我们通过使用memoryview对象实现高效数据写入,但在某些情况下读取也同样适用。在Python中大部分 I/O 操作已经实现了buffer protocol机制。在本例中,我们并不需要memoryview对象,我可以请求 I/O 函数写入我们预定义好的对象:

>>> ba = bytearray(8)
>>> ba
bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x00')
>>> with open("/dev/urandom", "rb") as source:
...     source.readinto(ba)
... 
8
>>> ba
bytearray(b'`m.z\x8d\x0fp\xa1')

通过该机制,我们可以很简单写入到预定义的buffer中(在C语言中,你可能需要多次调用malloc())。

适用memoryview,你甚至可以将数据放入到内存区域任意点:

>>> ba = bytearray(8)
>>> # Reference the _bytearray_ from offset 4 to its end
>>> ba_at_4 = memoryview(ba)[4:]
>>> with open("/dev/urandom", "rb") as source:
... # Write the content of /dev/urandom from offset 4 to the end of the
... # bytearray, effectively reading 4 bytes only
...     source.readinto(ba_at_4)
... 
4
>>> ba
bytearray(b'\x00\x00\x00\x00\x0b\x19\xae\xb2')

buffer protocol是实现低内存开销的基础,具备很强的性能。虽然Python隐藏了所有的内存分配,开发者不需要关系内部是怎么样实现的。

可以再去了解一下array模块和struct模块是如何处理buffer protocol的,zero copy操作是相当高效的。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python使用Zero-Copy和Bu

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python使用Zero-Copy和Bu

无论你程序是做什么的,它经常都需要处理大量的数据。这些数据大部分表现形式为strings(字符串)。然而,当你对字符串大批量的拷贝,切片和修改操作时是相当低效的。为什么?让我们假设一个读取二进制数据的大文件示例,然后将部分数据拷贝到另外一个
2023-01-30

Python中copy()和deepcopy()怎么用

小编给大家分享一下Python中copy()和deepcopy()怎么用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!1、copy.copy()示例代码:import copyspam = [A,B,C,D,[1,2,3,
2023-06-25

如何在Python中使用copy模块

这篇文章给大家介绍如何在Python中使用copy模块,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。python可以做什么Python是一种编程语言,内置了许多有效的工具,Python几乎无所不能,该语言通俗易懂、容易
2023-06-14

使用 GitHub 和 Python

借助 GitHub 的网络钩子webhook,开发者可以创建很多有用的服务。从触发一个 Jenkins 实例上的 CI(持续集成) 任务到配置云中的机器,几乎有着无限的可能性。这篇教程将展示如何使用 Python 和 Flask 框架来搭建
2023-01-31

mosquitto和python的使用

http://publib.boulder.ibm.com/infocenter/wmqv7/v7r0/index.jsp?topic=/com.ibm.mq.amqtat.doc/tt00000_.htm 启动 mosquitto -c
2023-01-31

使用 OpenAI API 和 Python 使用 GPT-3

这篇文章主要介绍了使用 OpenAI API 和 Python 使用 GPT-3,在本文中,我们将使用GPT-3。我将向您展示如何访问它,并提供一些示例来说明您可以使用它做什么,以及您可以使用它构建什么样的应用程序,需要的朋友可以参考下
2023-03-06

使用IDLE和Python Shell编

1、IDLE(Python GUI)是一个功能完备的代码编辑器,允许在这个编辑器中编写代码。启动IDLE时。会显示“三个尖括号”提示符(>>>),可以输入代码。IDLE 提供了大量的特性,不过了解其中一小部分就能高校地使用IDLE。TAB键
2023-01-31

python MySQLdb安装和使用

MySQLdb是Python连接MySQL的模块,下面介绍一下源码方式安装MySQLdb:首先要下载下载:请到官方网站http://sourceforge.net/projects/mysql-python/或者点击链接下载http://d
2023-01-31

结合使用 Python 和 Rust

Rust 和 Python 的优势互补。可以使用 Python 进行原型设计,然后将性能瓶颈转移到 Rust 上。
PythonRust2024-11-30

python如何使用Arange和Linspace

这篇文章主要介绍了python如何使用Arange和Linspace,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Arange和LinspaceArange返回给定步长的等差
2023-06-27

python dropna()和notnull()怎么使用

在Python中,`dropna()`和`notnull()`是Pandas库中的两个常用函数。`dropna()`函数用于删除包含缺失值(NaN)的行或列。它的常用参数包括`axis`(指定删除行还是列,默认为行)、`subset`(指定
2023-08-17

Python和C++使用哪个好

本篇内容主要讲解“Python和C++使用哪个好”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python和C++使用哪个好”吧!Python是解释型语言,而C++不是C++的工作方式是,首先将
2023-06-16

Python中*args 和 **kwargs如何使用

本篇文章给大家分享的是有关Python中*args 和 **kwargs如何使用,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一 简介*args 和 **kwargs 主要用于
2023-06-04

python的os.mkdir和os.makedirs如何使用

这篇“python的os.mkdir和os.makedirs如何使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“pytho
2023-07-06

Python中str.format()和f-string的使用

本文主要介绍了Python中str.format()和f-string的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-02-27

Python中Dict和Set如何使用

这篇文章将为大家详细讲解有关Python中Dict和Set如何使用,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。 一、dictPython内置了字典:dict的支持,dict全称dictio
2023-06-15

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录