【问题标题】:python memoryview slower than expectedpython memoryview比预期慢
【发布时间】:2019-04-20 11:07:03
【问题描述】:

鉴于 Python 的缓冲协议的 memoryview 接口可以帮助减少制作临时数据副本的需要,我决定基于此 answer 到此 question 对其进行快速测试。

import time

expressions = ['list(b[i:i+1000])',
               'list(b[i:])',
               'b[i:]'
              ]

size = 1000000
x = b'x'*size
mv = memoryview(x)
for e in expressions:
    print(f"Expression: {e}")
    for b in (x, mv):
        l = len(b)
        start = time.time()
        for i in range(0, l, 1000):
            eval(e)
        end = time.time()
        print(f"Size: {size}, {type(b).__name__}, time: {end-start}")

结果:

$ python c:\temp\test_memoryview.py
Expression: list(b[i:i+1000])
Size: 1000000, bytes, time: 0.021999597549438477
Size: 1000000, memoryview, time: 0.03600668907165527
Expression: list(b[i:])
Size: 1000000, bytes, time: 5.3010172843933105
Size: 1000000, memoryview, time: 11.202003479003906
Expression: b[i:]
Size: 1000000, bytes, time: 0.2990117073059082
Size: 1000000, memoryview, time: 0.006985902786254883

前两个结果似乎是一个相当令人惊讶的结果。 我知道调用列表将涉及数据的副本,但我认为在切片内存视图而不是底层字节数组时,您可以保存临时副本。

【问题讨论】:

  • 您保存的副本只是所涉及的总开销的一小部分。尝试在内存视图上使用tolist 而不是调用list

标签: python memoryview


【解决方案1】:

您不能像对待 C 或 C++ 那样思考 Python。额外副本的常量因子开销远低于支持 Python 的所有动态特性所涉及的常量因子开销,尤其是在 CPython 中没有 JIT 的情况下。一旦考虑到为避免该副本而必须更改的其他内容,您就不能假设保存一份副本实际上会有所帮助。

在这种情况下,几乎所有的工作都在列表转换中。您保存的副本毫无意义。比较b[i:]list(b[i:]) 的时间,你会发现即使切片执行复制,切片也只占运行时间的百分之几。

您保存的副本无关紧要,因为它基本上只是一个memcpy。相比之下,列表转换需要在字节串或内存视图上创建一个迭代器,反复调用迭代器的tp_iternext槽,获取内存的原始字节对应的int对象等,成本要高得多。 memoryview 的成本更高,因为 memoryview 对象必须支持多维形状和非字节数据类型,并且由于 memoryview 实现没有专用的__iter__ 实现,所以它通过通用的基于序列的回退迭代, 比较慢。

您可以通过使用 memoryview 的 tolist 方法而不是调用 list 来节省一些时间。这跳过了一堆迭代协议开销,并允许一些检查只进行一次而不是每个项目一次。在我的测试中,这几乎与在字节串上调用 list 一样快。

【讨论】:

    猜你喜欢
    • 2012-09-29
    • 2015-09-18
    • 2012-03-22
    • 1970-01-01
    • 2016-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-21
    相关资源
    最近更新 更多