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

Rui*_*lho 3 python sockets streaming pickle

流媒体端不断发送 2048 字节的声音样本以及作为整数的时间,一起发送到使用 pickle.dumps 进行腌制的元组中,然后将其以 UDP 数据包发送到接收器,然后接收器将其解开并缓冲它然后播放声音样本。

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

当我在python 2.7中运行streamer时,速度更快了!我觉得 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)
Run Code Online (Sandbox Code Playgroud)

接收端:

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
Run Code Online (Sandbox Code Playgroud)

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

奇怪的是,在使用 pickle format 2 的 python 2.7 上,我得到了 5933 个字节,几乎是原来的 3 倍。

为什么这个差别这么大呢?

我应该制定一个协议并附加这些字节吗?我本来可以的,但在我找到泡菜之后,我相信它会起作用。

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

谢谢。

aba*_*ert 5

首先,作为一般规则,协议、库等的主要新版本有重大改进并不奇怪。否则,为什么会有人费尽心思去完成所有工作来创建它们呢?

\n\n

但您可能正在寻找细节。

\n\n
\n\n

在我们讨论其他内容之前,您的大问题是您没有比较协议 2 和协议 3,而是比较协议 0 和协议 3。请注意pickletools.dumps下面转储中的最后一行:highest protocol among opcodes = 2。如果您看到0而不是2There,则意味着您正在使用协议 0。协议 0 是为人类可读性而设计的(嗯,至少是在没有像 之类的库的情况下的人类可调试性pickletools),而不是为了紧凑性。特别是,它将反斜杠转义不可打印的 ASCII 字节,将其中的大部分扩展为 4 个字符。

\n\n

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

\n\n

如果您将代码更改为pickle.dumps(msg, protocol=pickle.HIGHEST_PROTOCOL)(或只是protocol=-1),您将得到 2 和 4,而不是 0 和 3。 2.x 可能仍会大于 3.x,原因如下所述,但远不及 3.x与您现在看到的规模相同。

\n\n

如果您确实想要奇偶校验,如果协议 2 结果对您来说足够紧凑,您可能需要显式使用protocol=2.

\n\n

如果你想明确地只使用 2 或 3,正如你所认为的那样,没有直接的方法来编写它,但protocol=min(3, pickle.HIGHEST_PROTOCOL)会这样做。

\n\n
\n\n

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

\n\n

让我们使用较短的字符串,以便于查看:

\n\n
>>> t = (1, string.ascii_lowercase.encode(\'ascii\'))\n>>> p2 = pickle.dumps(t, protocol=2)\n>>> p3 = pickle.dumps(t, protocol=3)\n>>> len(p2), len(p3)\n78, 38\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

现在,让我们看看泡菜里有什么。(您可能想pickletools.dis(p2, annotate=1)在自己的解释器中使用,但由于大多数信息会滚动到屏幕边缘,因此这里没那么有用\xe2\x80\xa6)

\n\n
>>> pickletools.dis(p2)\n    0: \\x80 PROTO      2\n    2: K    BININT1    1\n    4: c    GLOBAL     \'_codecs encode\'\n   20: q    BINPUT     0\n   22: X    BINUNICODE \'abcdefghijklmnopqrstuvwxyz\'\n   53: q    BINPUT     1\n   55: X    BINUNICODE \'latin1\'\n   66: q    BINPUT     2\n   68: \\x86 TUPLE2\n   69: q    BINPUT     3\n   71: R    REDUCE\n   72: q    BINPUT     4\n   74: \\x86 TUPLE2\n   75: q    BINPUT     5\n   77: .    STOP\nhighest protocol among opcodes = 2\n
Run Code Online (Sandbox Code Playgroud)\n\n

如您所见,协议 2 存储bytes为 Unicode 字符串加编解码器。

\n\n
>>> pickletools.dis(p3)\n    0: \\x80 PROTO      3\n    2: K    BININT1    1\n    4: C    SHORT_BINBYTES b\'abcdefghijklmnopqrstuvwxyz\'\n   32: q    BINPUT     0\n   34: \\x86 TUPLE2\n   35: q    BINPUT     1\n   37: .    STOP\nhighest protocol among opcodes = 3\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\xa6 但协议 3bytes使用协议 2 中不存在的新操作码将它们存储为对象。

\n\n
\n\n

更详细地说:

\n\n

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

\n\n

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

\n\n

因为协议 1 和 2 没有BINBYTES,bytes实际上被存储为对和_codecs.encode的结果的调用作为参数。(为什么是 Latin-1?可能是因为它是将每个字节映射到单个 Unicode 字符的最简单的编解码器。)b.decode(\'latin-1\')u\'latin-1\'

\n\n

这增加了 40 个字节的固定开销(这说明了我的p2p3).

\n\n

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

\n\n

请注意,协议 1 及更高版本中一种BINSTRING类型,它与 非常相似BINBYTES,但它被定义为以默认编码存储字节,这几乎没有用处。在 2.x 中,这并不会真正产生影响,因为decode无论如何你都不会去获得一个str,但我的猜测是 2.6+ 不会将它用于 3.x 兼容性。

\n\n

还有一种STRING类型可以追溯到协议 0,它repr在字符串上存储 ASCII 编码。我认为它从未在协议 1 及更高版本中使用过。这当然会将任何不可打印的 ASCII 字节放大为 2 或 4 字节反斜杠转义。

\n