打包字节串的最优写法

背景

最近在看一个 dns 库,它里面有一个需求就是要把一个整数(小于 64) 转换成字节码,它的写法是这样的。

In [1]: bytes([3])                                                                                                             
Out[1]: b'\x03'

sqlpy


我的写法

如果这个让我来写,我会这样写(事实上我也确实没有想到它这种写法)。

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 字节码层面是一样的,性能上也没有差别。