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

标签 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

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

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

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

我应该只制作一个协议(protocol)并附加这些字节吗?我本来可以的,但是在我找到 pickle 之后,我觉得它会起作用。

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

谢谢。

最佳答案

首先,作为一般规则,协议(protocol)、库等的主要新版本有重大改进应该不足为奇。否则,为什么会有人费心去做所有的工作来创造它们呢?

但您可能正在寻找细节。

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

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

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

如果您真的想要奇偶校验,如果 protocol-2 结果对您来说足够紧凑,您可能需要明确使用 protocol=2 .

如果您想明确地只使用 2 或 3,就像您认为的那样,没有直接的方法来写,但是 protocol=min(3, pickle.HIGHEST_PROTOCOL)会做的。

pickletools 模块(以及从文档链接的源代码中的注释)使探索差异变得容易。

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

>>> 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

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

现在,让我们看看 pickle 里有什么。 (您可能希望在自己的解释器中使用 pickletools.dis(p2, annotate=1),但由于大部分信息都滚动到屏幕边缘,所以这里没有那么有用……)
>>> 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

如您所见,协议(protocol) 2 存储 bytes作为一个 Unicode 字符串加上一个编解码器。
>>> 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

…但协议(protocol) 3 将它们存储为 bytes对象,使用协议(protocol) 2 中不存在的新操作码。

更详细地说:
BINUNICODE操作码系列采用 Unicode 字符串并将其存储为以长度为前缀的 UTF-8。
BINBYTES一系列操作码接受一个字节字符串并将其存储为以长度为前缀的字节。

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

这增加了 40 字节的固定开销(这说明了我的 p2p3 之间的差异)。

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

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

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

关于python - pickle 协议(protocol) 2 和 3 之间的巨大大小(以字节为单位)差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26515272/

相关文章:

python - 模式匹配并用 if else 循环替换字符串

Python 3 - 类型错误 : string indices must be integers/conditional statement

python - 准确测试 Pypy 与 CPython 的性能

sql - 当我从 Azure 流作业插入数据时,如何在 CosmosDB 中使用 GUID 作为 ID

音频托管平台/服务器端软件

youtube - 如何构建一个简单的视频流服务器?

python - Python(CPython 实现)函数可以容纳多少个局部变量?

java - 使用java测量DNS查找时间

sockets - 关闭套接字时是否需要从epoll/kqueue中注销fd?

sockets - 为什么使用 UDP 套接字时不需要调用 listen() 函数?