打包字节串的最优写法
背景
最近在看一个 dns 库,它里面有一个需求就是要把一个整数(小于 64) 转换成字节码,它的写法是这样的。
In [1]: bytes([3])
Out[1]: b'\x03'
我的写法
如果这个让我来写,我会这样写(事实上我也确实没有想到它这种写法)。
In [1]: import struct
In [2]: struct.pack("<B",3)
Out[2]: b'\x03'
两种写法的比较
前者直接使用了系统类型一行代码解决,后者使用标准库两行代码解决,哪个在性能上更加优秀呢,那跑个分吧。
import struct
import time
def using_bytes():
result = []
for i in range(64):
result.append(bytes([i]))
return result
def using_struct():
result = []
for i in range(64):
result.append(struct.pack('<B',i))
return result
def main():
bytes_start_at = time.time()
for i in range(1000):
using_struct()
bytes_end_at = time.time()
print(f"bytes cost {bytes_end_at - bytes_start_at}")
struct_start_at = time.time()
for i in range(1000):
using_struct()
struct_end_at = time.time()
print(f"struct cost {struct_end_at - struct_start_at}")
if __name__ == "__main__":
main()
运行效果。
python3 main.py
bytes cost 0.011537790298461914
struct cost 0.010931015014648438
哈哈哈,它的代码比我的短,但是没我的效率高。
比较字节码
为了明确第二种写法快在哪里,现在准备分析二进制码。
In [5]: dis.dis(bytes([3]))
0 ROT_THREE
In [6]: import struct
In [7]: dis.dis(struct.pack("<B",3))
0 ROT_THREE
哦老天,字节码是一样的。但是又是缓存的问题?那交换一下执行次序。
def main():
struct_start_at = time.time()
for i in range(1000):
using_struct()
struct_end_at = time.time()
print(f"struct cost {struct_end_at - struct_start_at}")
bytes_start_at = time.time()
for i in range(1000):
using_struct()
bytes_end_at = time.time()
print(f"bytes cost {bytes_end_at - bytes_start_at}")
运行效果。
python3 main.py
struct cost 0.011535882949829102
bytes cost 0.011312246322631836
可以看到在交换了顺序之后现在结果反过来了,这也进一步验证了字节码的分析。
结论
bytes([3])
和 struct.pack("<B",3)
这两种写法在 python 字节码层面是一样的,性能上也没有差别。