设为首页 加入收藏

TOP

Python使用Zero-Copy和Buffer Protocol实现高性能编程(一)
2019-01-24 18:08:37 】 浏览:183
Tags:Python 使用 Zero-Copy Buffer Protocol 实现 高性能 编程

无论你程序是做什么的,它经常都需要处理大量的数据。这些数据大部分表现形式为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       p
首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇#6 Python数据类型及运算 下一篇Python练手例子(2)

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目