$x_{1a0b1}x$
如果你运行那个,你会得到:
$x_{1a1b1}x$
每名写入者占用1.2MB,每名读取者占用45KB,这很多了,尤其是在使用WebSockets时,大部分消息都很小,平均大约是512字节。为什么compress/flate会分配这么多内存,有没有方法可以减少它?
gzip(虽然我没有在基准测试中包含它,但是zlib使用的内存要少得多)。
相关:
$x_{1e0f1}x$
$x_{1e1f1}x$
cfh9epnr1#
在我的手机上,但是大多数gzip(也许还有flate?)类型都有一个Reset方法,你可以调用它来允许它们的重用。这应该会有很大帮助。如果有原因你不能使用Reset,或者一个关键类型缺少一个Reset选项,请详细说明。
inkz8wg92#
在我的手机上,但大多数gzip(也许还有flate?)类型都有一个Reset方法,你可以调用它来允许它们的重用。这应该会有很大帮助。如果有原因你不能使用Reset,或者一个关键类型缺少Reset选项,请详细说明。是的,它们两者都确实有一个Reset方法,并且确实有所帮助。然而,与compress/gzip或compress/zlib相比,flate使用的内存仍然非常多。
yfjy0ee73#
然而,与compress/gzip或compress/zlib相比,flate使用的内存仍然非常多。由于gzip和zlib都使用deflate并存储*flate.Writer,你的基准测试具有误导性。gzip和zlib在数据写入流之前不会分配压缩器,所以如果你只写入一次,你会看到相同的数字。而且在实际使用中,结果也是一样的。
*flate.Writer
gzip
zlib
但是,让我们把它放在上下文中考虑。deflate压缩确实会进行大量的预先分配。这些分配是为了在不进行额外分配的情况下进行标准操作,以及为什么“重置”可用来重用。
正如我在gorilla ticket上写的:对于压缩级别1(最快),这也意味着许多不必要的分配。几年前,我进行了一个实验,以更有选择地进行分配。这主要是为了在使用Reset时不使用它,但它将产生较少分配的副作用。
Reset
未完成的PR在这里:klauspost/compress#70 - 注意,“级别2”相当于stdlib级别1。
一个更简单的优化可能是:对于级别1、0和-2,压缩器中的以下数组不需要:hashHead(512KB) hashPrev(128KB)。将其添加到仅在需要时分配的结构中应该是相当简单的。hashMatch(1KB)也不需要,但现在我们开始涉及到小事情了。
hashHead
hashPrev
hashMatch
lztngnrs4#
由于gzip和zlib都使用deflate算法并存储*flate.Writer,所以你的基准测试具有误导性。gzip和zlib在数据写入流之前不会分配压缩器,因此如果你只进行一次写入操作,你会看到相同的数字。在实际应用中,结果也是一样的。这很有道理。但是,让我们将这个问题放在上下文中考虑。deflate压缩确实会进行大量的预先分配。这些分配是为了在不进行额外分配的情况下进行标准操作,以及为什么'Reset'可用来重用。对于写入如此小的消息,我认为1.2 MB是一个非常高的价格。我不是压缩算法的Maven,但缓冲区是否可以动态调整以根据需要增长,而不是总是分配这么多?一个更简单的优化可能是:对于级别1、0和-2,压缩器中不需要以下数组:hashHead(512KB)和hashPrev(128KB)。将其添加到仅在需要时分配的结构中应该很简单。hashMatch(1KB)也不需要,但现在我们开始讨论细节了。这将产生巨大的差异,但仍然为每个写入者留下560 KB的空间。在我看来,这对于WebSocket的使用场景仍然显得非常过分。
tmb3ates5#
以下是上述PR的简化版本:
- [klauspost/compress#107](https://github.com/klauspost/compress/pull/107) + - [klauspost/compress#107](https://github.com/klauspost/compress/pull/107) @@ -2,6 +2,7 @@ # This is a comment def foo(): pass + return True }
xa9qqrwz6#
缓冲区是否可以动态调整以满足需求?这将带来巨大的性能损失。大分配包括哈希表和链表。哈希表是一种map[uint16]int32查找,但它稀疏地填充,因为这允许非常快的查找,无需边界检查,因为编译器知道它的大小。在stdlib "level 1"使用自己的(较小的,128KB)哈希表,因此为更昂贵的级别分配的资源未被使用。这将产生巨大的差异,但仍为每个写入者留下560 KB。在我看来,这对于WebSocket用例仍然显得非常过分。让我们分解剩下的部分:64KB在d.window中分配。这是累积输入,直到我们有足够的数据块。这可以是预先分配较少的空间,但这意味着随着内容被写入而分配空间。64KB用于“tokens”,即压缩阶段的输出。这可以少一些,但也会导致压缩过程中的分配。无论输入有多大,都需要Huffman树和直方图。只有“level -2”(HuffmanOnly)可以使用略少的空间。最后,我想出的是输出缓冲区,大小为256字节。在那里没有什么可获得的。让我看看我能否修复你的基准测试,以获得一些实际数字。
map[uint16]int32
d.window
4smxwvx57#
io.Writer接口使得更详细的优化变得困难。即使你只发送几个字节,我们也无法知道你不会发送更多。一个合理的补充是Encode(src, dst []byte) []byte,它允许你发送整个想要压缩的内容。这将允许压缩器选择合适的压缩方案并共享压缩器。
io.Writer
Encode(src, dst []byte) []byte
du7egjpx8#
我已经更新了 klauspost/compress#107,其中包含了剩余的实际数字,以下是一个实际基准的摘要:https://gist.github.com/klauspost/f5df3a3522ac4bcb3bcde448872dffe6大部分剩余的分配用于Huffman表生成器,这在无论输入大小的情况下都是不可避免的。再次注意,我的库中的“level 2”是stdlib中的“level 1”。所以,stdlib的基线大约是540K。如果你切换到我的库,那么对于level 1来说,大约是340KB。
w46czmvw9#
来自@klauspost的gorilla/websocket#203(评论)的一些令人兴奋的更新。
9条答案
按热度按时间cfh9epnr1#
在我的手机上,但是大多数gzip(也许还有flate?)类型都有一个Reset方法,你可以调用它来允许它们的重用。这应该会有很大帮助。如果有原因你不能使用Reset,或者一个关键类型缺少一个Reset选项,请详细说明。
inkz8wg92#
在我的手机上,但大多数gzip(也许还有flate?)类型都有一个Reset方法,你可以调用它来允许它们的重用。这应该会有很大帮助。如果有原因你不能使用Reset,或者一个关键类型缺少Reset选项,请详细说明。
是的,它们两者都确实有一个Reset方法,并且确实有所帮助。
然而,与compress/gzip或compress/zlib相比,flate使用的内存仍然非常多。
yfjy0ee73#
然而,与compress/gzip或compress/zlib相比,flate使用的内存仍然非常多。由于gzip和zlib都使用deflate并存储
*flate.Writer
,你的基准测试具有误导性。gzip
和zlib
在数据写入流之前不会分配压缩器,所以如果你只写入一次,你会看到相同的数字。而且在实际使用中,结果也是一样的。但是,让我们把它放在上下文中考虑。deflate压缩确实会进行大量的预先分配。这些分配是为了在不进行额外分配的情况下进行标准操作,以及为什么“重置”可用来重用。
正如我在gorilla ticket上写的:对于压缩级别1(最快),这也意味着许多不必要的分配。几年前,我进行了一个实验,以更有选择地进行分配。这主要是为了在使用
Reset
时不使用它,但它将产生较少分配的副作用。未完成的PR在这里:klauspost/compress#70 - 注意,“级别2”相当于stdlib级别1。
一个更简单的优化可能是:对于级别1、0和-2,压缩器中的以下数组不需要:
hashHead
(512KB)hashPrev
(128KB)。将其添加到仅在需要时分配的结构中应该是相当简单的。hashMatch
(1KB)也不需要,但现在我们开始涉及到小事情了。lztngnrs4#
由于gzip和zlib都使用deflate算法并存储*flate.Writer,所以你的基准测试具有误导性。gzip和zlib在数据写入流之前不会分配压缩器,因此如果你只进行一次写入操作,你会看到相同的数字。在实际应用中,结果也是一样的。
这很有道理。
但是,让我们将这个问题放在上下文中考虑。deflate压缩确实会进行大量的预先分配。这些分配是为了在不进行额外分配的情况下进行标准操作,以及为什么'Reset'可用来重用。
对于写入如此小的消息,我认为1.2 MB是一个非常高的价格。我不是压缩算法的Maven,但缓冲区是否可以动态调整以根据需要增长,而不是总是分配这么多?
一个更简单的优化可能是:对于级别1、0和-2,压缩器中不需要以下数组:hashHead(512KB)和hashPrev(128KB)。将其添加到仅在需要时分配的结构中应该很简单。hashMatch(1KB)也不需要,但现在我们开始讨论细节了。
这将产生巨大的差异,但仍然为每个写入者留下560 KB的空间。在我看来,这对于WebSocket的使用场景仍然显得非常过分。
tmb3ates5#
以下是上述PR的简化版本:
xa9qqrwz6#
缓冲区是否可以动态调整以满足需求?
这将带来巨大的性能损失。大分配包括哈希表和链表。哈希表是一种
map[uint16]int32
查找,但它稀疏地填充,因为这允许非常快的查找,无需边界检查,因为编译器知道它的大小。在stdlib "level 1"使用自己的(较小的,128KB)哈希表,因此为更昂贵的级别分配的资源未被使用。
这将产生巨大的差异,但仍为每个写入者留下560 KB。在我看来,这对于WebSocket用例仍然显得非常过分。
让我们分解剩下的部分:
64KB在
d.window
中分配。这是累积输入,直到我们有足够的数据块。这可以是预先分配较少的空间,但这意味着随着内容被写入而分配空间。64KB用于“tokens”,即压缩阶段的输出。这可以少一些,但也会导致压缩过程中的分配。
无论输入有多大,都需要Huffman树和直方图。只有“level -2”(HuffmanOnly)可以使用略少的空间。
最后,我想出的是输出缓冲区,大小为256字节。在那里没有什么可获得的。
让我看看我能否修复你的基准测试,以获得一些实际数字。
4smxwvx57#
io.Writer
接口使得更详细的优化变得困难。即使你只发送几个字节,我们也无法知道你不会发送更多。一个合理的补充是
Encode(src, dst []byte) []byte
,它允许你发送整个想要压缩的内容。这将允许压缩器选择合适的压缩方案并共享压缩器。du7egjpx8#
我已经更新了 klauspost/compress#107,其中包含了剩余的实际数字,以下是一个实际基准的摘要:https://gist.github.com/klauspost/f5df3a3522ac4bcb3bcde448872dffe6
大部分剩余的分配用于Huffman表生成器,这在无论输入大小的情况下都是不可避免的。再次注意,我的库中的“level 2”是stdlib中的“level 1”。所以,stdlib的基线大约是540K。如果你切换到我的库,那么对于level 1来说,大约是340KB。
w46czmvw9#
来自@klauspost的gorilla/websocket#203(评论)的一些令人兴奋的更新。