在 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
我有一些 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
字节值 (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