zlib,deflate:需要分配多少内存?

13
我正在使用 zlib 压缩文本数据流。文本数据以块的形式传输,每个块都会调用 deflate(),其中 flush 设置为 Z_NO_FLUSH。当检索到所有块后,将使用 flush 设置为 Z_FINISH 调用 deflate()
自然地,deflate() 并不会在每次调用时产生压缩输出。它内部积累数据以实现高压缩率。这很好!每次 deflate() 生成压缩输出时,该输出都将追加到数据库字段中 - 这是一个缓慢的过程。
但是,一旦 deflate() 生成了压缩数据,该数据可能无法适合提供的输出缓冲区 deflate_out 中。因此,需要多次调用 deflate()。这就是我想要避免的:
是否有一种方法可以使 deflate_out 总是足够大,以便每次决定生成输出时,deflate() 都可以将所有压缩数据存储在其中?
注: - 未知未压缩数据的总大小。如上所述,未压缩数据以块的形式传输,压缩数据也以块的形式追加到数据库字段中。 - 在 include 文件 zconf.h 中,我找到了以下注释。这也许就是我要找的吗?即 (1 << (windowBits+2)) + (1 << (memLevel+9))deflate() 可能产生的压缩数据的最大大小(以字节为单位)吗?
/* The memory requirements for deflate are (in bytes):
            (1 << (windowBits+2)) +  (1 << (memLevel+9))
 that is: 128K for windowBits=15  +  128K for memLevel = 8  (default values)
 plus a few kilobytes for small objects. For example, if you want to reduce
 the default memory requirements from 256K to 128K, compile with
     make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
 Of course this will generally degrade compression (there's no free lunch).

   The memory requirements for inflate are (in bytes) 1 << windowBits
 that is, 32K for windowBits=15 (default value) plus a few kilobytes
 for small objects.
*/

请参阅http://stackoverflow.com/questions/4936255/zlib-how-to-dimension-avail-out。 - nos
2
@nos:只有在已知输入大小的情况下,这才是有用的。 - Eugen Rieck
我在zconf.h的注释中读到的是压缩所需的内存要求,而不是输出缓冲区的大小。话虽如此,似乎逻辑上讲,输出缓冲区的上限应该是总内存需求(以上例子中为128K+128K+"几千字节")+头部长度(40字节)。 - Eugen Rieck
2个回答

7

deflateBound()只有在您完成所有压缩的单个步骤或者强制deflate压缩当前可用于它并发出所有输入数据的压缩数据时才有用。您需要使用flush参数(如Z_BLOCK、Z_PARTIAL_FLUSH等)这样做。

如果要使用Z_NO_FLUSH,则尝试预测下一个调用中deflate()可能发出的最大输出量变得更加困难和低效。您不知道在发出上一次压缩数据突发时已经消耗了多少输入,因此需要假设几乎没有,缓冲区大小会不必要地增长。无论您如何估计最大输出,都将进行许多不必要的malloc或realloc,这是低效的。

避免调用更多输出没有意义。如果您只需在deflate()上循环,直到它对您没有更多输出,那么可以使用一次malloc的固定输出缓冲区。这就是deflate()和inflate()接口设计的使用方式。您可以查看http://zlib.net/zlib_how.html以获取如何使用接口的详细文档示例。

顺便提一下,最新版本的zlib(1.2.6)中有一个deflatePending()函数,可以让您知道deflate()有多少输出等待传送。


非常感谢您提供的详细答案!为了预测下一次调用deflate()所需的输出缓冲区大小,我考虑将deflatePending()报告的大小和deflateBound()返回的值相加。这类似于@EugenRieck的建议。然而,据我所知,这不是一个好主意,因为deflateBound()仅在传递要压缩的整个输入的大小时才能正常工作。也就是说,deflateBound()的文档没有说明它适用于输入块。 - feklee
1
deflateBound() 可以用于输入数据块,但前提是所有先前的输入都已被压缩和发射。这只能通过使用除 Z_NO_FLUSH 以外的刷新选项并在之前的调用中消耗所有输出来保证。在这种情况下,当使用 Z_BLOCK 或 Z_PARTIAL_FLUSH 时,deflatePending() 将非常有用,因为它们可能会留下一些位。当使用 Z_NO_FLUSH 时,deflateBound() + deflatePending() 将缺少第三个部分,即先前 deflate() 调用中消耗但尚未压缩和发射的输入大小。 - Mark Adler

2
在查找线索时,我偶然发现了一个资源。
/* =========================================================================
 * Flush as much pending output as possible. All deflate() output goes
 * through this function so some applications may wish to modify it
 * to avoid allocating a large strm->next_out buffer and copying into it.
 * (See also read_buf()).
 */
local void flush_pending(strm)
    z_streamp strm;
{
    unsigned len = strm->state->pending;
...

跟踪void flush_pending()在deflate()中的使用,显示出在流的中间所需的输出缓冲区的上限。
strm->state->pending + deflateBound(strm, strm->avail_in)

第一部分解释了从之前对deflate()函数的调用中仍留在管道中的数据,第二部分解释了长度为avail_in的未经处理的数据。


你在对我现已删除的答案的评论中是正确的。我忘记了内部状态。出于好奇,我在快速测试中查看了那个待处理值的第一次调用deflate之后。avail_in为零,avail_out为2,pending为零(0)。它似乎没有反映实际的待处理数据量。下一个调用deflate以刷新它时,将约8K转储到输出。因此,在至少一种情况下,这可能不是准确的测量值... - Mark Wilkins
你说 strm->state->pending管道中仍然存在的数据 的大小。如果我理解正确,那么这个大小会随着每次调用 deflate() 而增加,直到达到一个未知的上限。而这个上限恰恰是我正在寻找的。那么这有什么帮助呢?我有什么遗漏的吗? - feklee
我的意思是,如果你给deflate()一个大小为strm->state->pending + deflateBound(strm, strm->avail_in)的缓冲区,它永远不会用完缓冲区空间。 - Eugen Rieck
我明白了。所以在调用 deflate() 之前,需要为 strm->next_out 分配 strm->state->pending + deflateBound(strm, strm->avail_in) 字节的内存。感谢你找出了这个问题!但我仍然不确定是否应该依赖这种方法。毕竟它并没有作为 zlib API 的一部分被记录下来。 - feklee
zlib.h 中的文档强烈建议不要访问 strm->statestruct internal_state FAR *state; /* not visible by applications */ - feklee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接