在 python 中交换字节的有效方法

Efficient way to swap bytes in python

我有一些 bytearray,长度为 2*n:

a1 a2 b1 b2 c1 c2

我需要在每个 2 字节的字中切换字节序,并使:

a2 a1 b2 b1 c2 c1

现在我使用下一种方法,但它对我的任务来说非常慢:

converted = bytearray([])
for i in range(int(len(chunk)/2)):
   converted += bytearray([ chunk[i*2+1], chunk[i*2] ])

是否可以通过调用某些 system/libc 函数来切换 bytearray 的字节序?


好的,感谢大家,我定时提一些建议:

import timeit

test = [
"""
converted = bytearray([])
for i in range(int(len(chunk)/2)):
   converted += bytearray([ chunk[i*2+1], chunk[i*2] ])
""",
"""
for i in range(0, len(chunk), 2):
    chunk[i], chunk[i+1] = chunk[i+1], chunk[i]
""",
"""
byteswapped = bytearray([0]) * len(chunk)
byteswapped[0::2] = chunk[1::2]
byteswapped[1::2] = chunk[0::2]
""",
"""
chunk[0::2], chunk[1::2] = chunk[1::2], chunk[0::2]
"""
]

for t in test:
    print(timeit.timeit(t, setup='chunk = bytearray([1]*10)'))

结果是:

$ python ti.py
11.6219761372
2.61883187294
3.47194099426
1.66421198845

因此,步长为 2 的同步切片分配现在是最快的。也感谢F先生的详细讲解,但由于numpy

,我还没有尝试过

您可以使用步长为 2 的切片赋值:

byteswapped = bytearray(len(original))
byteswapped[0::2] = original[1::2]
byteswapped[1::2] = original[0::2]

或者如果您想就地进行:

original[0::2], original[1::2] = original[1::2], original[0::2]

时间显示,对于大型数组,切片大规模优于 Python 级循环:

>>> timeit.timeit('''
... for i in range(0, len(chunk), 2):
...     chunk[i], chunk[i+1] = chunk[i+1], chunk[i]''',
... 'chunk=bytearray(1000)')
81.70195105159564
>>>
>>> timeit.timeit('''
... byteswapped = bytearray(len(original))
... byteswapped[0::2] = original[1::2]
... byteswapped[1::2] = original[0::2]''',
... 'original=bytearray(1000)')
2.1136113323948393
>>>
>>> timeit.timeit('chunk[0::2], chunk[1::2] = chunk[1::2], chunk[0::2]', 'chunk=
bytearray(1000)')
1.79349659994989

对于小型数组,切片仍然优于显式循环,但区别并不大:

>>> timeit.timeit('''
... for i in range(0, len(chunk), 2):
...     chunk[i], chunk[i+1] = chunk[i+1], chunk[i]''',
... 'chunk=bytearray(10)')
1.2503637694328518
>>>
>>> timeit.timeit('''
... byteswapped = bytearray(len(original))
... byteswapped[0::2] = original[1::2]
... byteswapped[1::2] = original[0::2]''',
... 'original=bytearray(10)')
0.8973060929306484
>>>
>>> timeit.timeit('chunk[0::2], chunk[1::2] = chunk[1::2], chunk[0::2]', 'chunk=
bytearray(10)')
0.6282232971918802

如果你使用 numpy 的 frombuffer 函数,你可以构造一个 numpy ndarray 实际共享 bytearray 的物理内存,然后可以就地完成交换操作而不是副本。

您可以直接在 byt 上简单地执行相同的索引,例如

byt[i] = byt[i+1]

但是在 numpy 中获取具有显式类型的缓冲数组的句柄通常可以让您做更多的事情,尤其是 bytearray 本身就非常有限。

不过要小心。即使在 Python 级别,bytearray 表示无符号 8 位整数值 (0-255),实际的底层 C 实现在 bytearrayobject.h 中使用普通的 char字节值 ()。如果为此使用 numpy,您可能需要为 frombuffer 指定可选的 dtype 参数,其中 dtype=numpy.ubyte.

import numpy as np
byt = bytearray(range(256))
npbyt = np.frombuffer(byt, dtype=np.ubyte)

for i in range(0, len(npbyt)-1, 2):
    temp = npbyt[i]
    npbyt[i] = npbyt[i+1]
    npbyt[i+1] = temp

print(list(byt))

它打印

[1, 
 0,
 3,
 2,
 5,
 4,
 ...
 255,
 254] 

我 运行 在做一个名为 buffersort 的小项目时遇到了其中的一些问题,它可以对实现可写缓冲区的 Python 对象执行就地排序协议,就像 bytearray 所做的那样。

如果您有兴趣从那里获取 Cython 源代码,可以使用一个简单的辅助函数 _swap 来轻松完成您想要的操作。不过,对于您的用例来说,这可能太过分了。

您也可以原地交换它们并使用原始数组。

chunk = bytearray([1,2,3,4])

for i in range(0, len(chunk), 2):
    chunk[i], chunk[i+1] = chunk[i+1], chunk[i]

来自

中的一些回答
import struct

def htonl_slice(data):
    byteswapped = bytearray(4)
    byteswapped[0::4] = data[3::4]
    byteswapped[1::4] = data[2::4]
    byteswapped[2::4] = data[1::4]
    byteswapped[3::4] = data[0::4]
    return byteswapped

def htonl_slice_2(data):
    byteswapped = bytearray(len(data))
    byteswapped[0::4] = data[3::4]
    byteswapped[1::4] = data[2::4]
    byteswapped[2::4] = data[1::4]
    byteswapped[3::4] = data[0::4]
    return byteswapped

def htonl_struct(data):
    return struct.pack(">L", struct.unpack("<L", data)[0])

def swap32(data):
    return [struct.unpack("<I", struct.pack(">I", i))[0] for i in data]

def htonl_shift(x):
    return (((x << 24) & 0xFF000000) |
            ((x <<  8) & 0x00FF0000) |
            ((x >>  8) & 0x0000FF00) |
            ((x >> 24) & 0x000000FF))    

def test(self):

    data = [struct.pack('<L', i) for i in range(1000000)]

    start = time.time()
    for d in data:
        x = htonl_slice(d)
    end = time.time()
    print("htonl_slice %f" % (end - start))
    
    start = time.time()
    for d in data:
        x = htonl_struct(d)
    end = time.time()
    print("htonl_struct %f" % (end - start))

    data = [i for i in range(1000000)]
    start = time.time()
    for d in data:
        x = htonl_shift(d)
    end = time.time()
    print("htonl_shift %f" % (end - start))

    start = time.time()
    x = swap32(data)
    end = time.time()
    print("swap32 %f" % (end - start))

    data = bytearray()
    for i in range(1000000):
        data += struct.pack('<L', i)

    start = time.time()
    x = htonl_slice_2(data)
    end = time.time()
    print("htonl_slice_2 %f" % (end - start))

结果是:

htonl_slice   3.041000
htonl_struct  0.626000
htonl_shift   0.864000
swap32        0.533000
htonl_slice_2 0.025000

我知道 htonl_shift 使用 int 而不是字节数组。

有趣的是,swap32 非常快,但对于 4 MB 的数组,htonl_slice_2 是迄今为止最快的。

我可以重复@user2357112 支持 Monica 的就地切片方法的结果。但是,如果数据大小是原来的十倍,则与 array.byteswap():

相比会慢两倍
>>> timeit.timeit('chunk[0::2], chunk[1::2] = chunk[1::2], chunk[0::2]', 'chunk=bytearray(10000)')
12.54664899999625

>>> timeit.timeit('a=array.array("H",chunk);a.byteswap();chunk=a.tobytes()', 'import array;chunk=bytearray(10000)')
6.398380300001008