pickle协议2和3之间的大小(以字节为单位)差异

时间:2014-10-22 19:12:41

标签: python sockets streaming pickle

流媒体方面一直发送一个2048字节的声音样本以及整数的时间,一起在使用pickle.dumps进行pickle的元组中,然后将UDP数据包发送到接收器,然后接收器将其解开,缓冲它,然后播放声音样本。

使用python 3一切都很好,预计接收器上的比特/秒速度。

当我在python 2.7中运行流光时,速度更快!我强硬的python 2在某种程度上更快。

然后我用wireshark检查了接收器收到的UDP数据包,它们比他们需要的要大。

流光方面:

while True:
    data = next(gen)
    print("data:{}".format(len(data)))
    stime +=1
    msg = (stime,data)
    payload = pickle.dumps(msg)
    print("payload:{}".format(len(payload)))
    bytes_sent = s.sendto(payload,addr)
    time.sleep(INTERVAL)

接收方:

while True:
    if stop_receiving.get():
        break
    try:
        (payload,addr) = self.sock.recvfrom(32767)      
        (t,data) = pickle.loads(payload,encoding="bytes")       
        if stime >= self.frame_time.get():
            self.frames.put((t,data))
    except socket.timeout:          
        pass

在python 3.4上使用pickle格式3,如果我pickle.dumps一个整数和2048字节的元组,我得到2063字节。

奇怪的是,在使用pickle格式2的python 2.7上,我获得5933字节,几乎是3倍。

为什么这种差异如此之大?

我应该制作一个协议并添加这些字节吗?我可以拥有,但是在我发现泡菜之后,我认为它会起作用。

Python文档还说可以使用压缩库来减小大小,但我不知道额外的时间开销是否会补偿。

谢谢。

1 个答案:

答案 0 :(得分:4)

首先,作为一般规则,协议,库等的主要新版本具有重大改进并不令人惊讶。否则,为什么有人会费心去做所有的工作来创造它们呢?

但你可能正在寻找细节。


在我们进入其他任何事情之前,您的主要问题是比较协议2和协议3,您正在比较协议0和协议3.请注意{中的最后一行{1}}转储如下:pickletools.dumps。如果你在那里看到highest protocol among opcodes = 2而不是0,那就意味着你正在使用协议0.协议0是为了人类可读性而设计的(好吧,至少人工可调试性没有像2这样的库) ,不是为了紧凑。特别是,它将反斜杠转义为非可打印的ASCII字节,将大部分字节扩展为4个字符。

那么,你为什么得到0而不是2?因为,出于向后兼容性原因,最高协议不是默认协议。 2.x中的默认值为0,3.x中的默认值为3。请参阅2.73.4的文档。

如果您将代码更改为pickletools(或仅pickle.dumps(msg, protocol=pickle.HIGHEST_PROTOCOL)),则会得到2和4而不是0和3. 2.x仍可能大于3。 x,由于下面解释的原因,但你现在看到的规模并不相同。

如果您真的想要奇偶校验,如果协议-2结果足够紧凑,您可能希望明确使用protocol=-1

如果您想明确地只使用2或3,就像您认为的那样,没有直接方式来编写它,但是protocol=2会这样做。


pickletools模块(以及源代码中的注释,从文档链接)可以轻松探索差异。

让我们使用更短的字符串,以便更容易查看:

protocol=min(3, pickle.HIGHEST_PROTOCOL)

所以,明显的区别仍然存在。

现在,让我们来看看泡菜中的含义。 (您可能希望在自己的解释器中使用>>> t = (1, string.ascii_lowercase.encode('ascii')) >>> p2 = pickle.dumps(t, protocol=2) >>> p3 = pickle.dumps(t, protocol=3) >>> len(p2), len(p3) 78, 38 ,但由于大部分信息都在屏幕边缘滚动,因此在这里没有用处...)

pickletools.dis(p2, annotate=1)

如您所见,协议2将>>> pickletools.dis(p2) 0: \x80 PROTO 2 2: K BININT1 1 4: c GLOBAL '_codecs encode' 20: q BINPUT 0 22: X BINUNICODE 'abcdefghijklmnopqrstuvwxyz' 53: q BINPUT 1 55: X BINUNICODE 'latin1' 66: q BINPUT 2 68: \x86 TUPLE2 69: q BINPUT 3 71: R REDUCE 72: q BINPUT 4 74: \x86 TUPLE2 75: q BINPUT 5 77: . STOP highest protocol among opcodes = 2 存储为Unicode字符串加编解码器。

bytes

...但是协议3使用协议2中不存在的新操作码将它们存储为>>> pickletools.dis(p3) 0: \x80 PROTO 3 2: K BININT1 1 4: C SHORT_BINBYTES b'abcdefghijklmnopqrstuvwxyz' 32: q BINPUT 0 34: \x86 TUPLE2 35: q BINPUT 1 37: . STOP highest protocol among opcodes = 3 对象。


更详细:

bytes系列操作码采用Unicode字符串并将其存储为长度为前缀的UTF-8。

BINUNICODE系列操作码采用字节字符串并将其存储为长度为前缀的字节。

由于协议1和协议2没有BINBYTESBINBYTES实际上存储为bytes_codecs.encode和{{{ 1}}作为参数。 (为什么使用Latin-1?可能是因为它是将每个字节映射到单个Unicode字符的最简单的编解码器。)

这增加了40个字节的固定开销(这说明了我的b.decode('latin-1')u'latin-1'之间的差异。)

更重要的是,对于您的情况,大多数非ASCII字节最终将是UTF-8的两个字节。对于随机字节,总开销约为51%。

请注意,协议1及更高版本中的 p2类型,与p3非常相似,但它被定义为在默认情况下存储字节编码,这几乎没用。在2.x中,这不会产生任何影响,因为你无论如何都不会BINSTRING获得BINBYTES,但我的猜测是2.6+不使用它3.x兼容性。

还有decode类型可追溯到协议0,协议0在字符串上存储ASCII编码的str。我不认为它曾在协议1和更高版本中使用过。这当然会将任何非可打印的ASCII字节炸成2或4字节的反斜杠转义。