如何在 Python 中有效地从大块中提取带有偏移量的字节?

问题描述

假设我有一个这样的字节块:

block = b'0123456789AB'

我想从每个 4 个字节的块中提取每个 3 个字节的序列并将它们连接在一起。上面块的结果应该是:

b'01245689A'  # 3,7 and B are missed

我可以用这样的脚本解决这个问题:

block = b'0123456789AB'
result = b''
for i in range(0,len(block),4):
    result += block[i:i + 3]
print(result)

但众所周知,Python 在使用这种 for 循环和字节连接时效率非常低,因此如果我将它应用于一个非常大的字节块,我的方法将永远不会结束。那么有没有更快的执行方式?

解决方法

使其可变并删除不需要的切片?

>>> tmp = bytearray(block)
>>> del tmp[3::4]
>>> bytes(tmp)
b'01245689A'

如果您的块很大并且您想删除几乎所有字节,那么收集您想要的内容可能会更快,类似于您的。虽然您的可能需要二次时间,但最好使用 join:

>>> b''.join([block[i : i+3] for i in range(0,len(block),4)])
b'01245689A'

(顺便说一句,根据 PEP 8,它应该是 block[i : i+3],而不是 block[i:i + 3],并且有充分的理由。)

虽然这会构建很多对象,但这可能是内存问题。对于您陈述的案例,它比您的快得多,但比我的 bytearray 慢得多。

使用 block = b'0123456789AB' * 100_000 进行基准测试(远小于您在下面评论中提到的 1GB):

    0.00 ms      0.00 ms      0.00 ms  baseline
15267.60 ms  14724.33 ms  14712.70 ms  original
    2.46 ms      2.46 ms      3.45 ms  Kelly_Bundy_bytearray
   83.66 ms     85.27 ms    122.88 ms  Kelly_Bundy_join

基准代码:

import timeit

def baseline(block):
    pass

def original(block):
    result = b''
    for i in range(0,4):
        result += block[i:i + 3]
    return result

def Kelly_Bundy_bytearray(block):
    tmp = bytearray(block)
    del tmp[3::4]
    return bytes(tmp)

def Kelly_Bundy_join(block):
    return b''.join([block[i : i+3] for i in range(0,4)])

funcs = [
    baseline,original,Kelly_Bundy_bytearray,Kelly_Bundy_join,]

block = b'0123456789AB' * 100_000
args = block,number = 10**0

expect = original(*args)
for func in funcs:
    print(func(*args) == expect,func.__name__)
print()

tss = [[] for _ in funcs]
for _ in range(3):
    for func,ts in zip(funcs,tss):
        t = min(timeit.repeat(lambda: func(*args),number=number)) / number
        ts.append(t)
        print(*('%8.2f ms ' % (1e3 * t) for t in ts),func.__name__)
    print()