zlib, deflate: сколько памяти выделить?

Я использую zlib для сжатия потока текстовых данных. Текстовые данные поступают фрагментами, и для каждого фрагмента вызывается deflate(), для сброса которого установлено значение Z_NO_FLUSH. После получения всех фрагментов вызывается deflate() с установленным для сброса значением Z_FINISH.

Естественно, deflate() не производит сжатый вывод при каждом вызове. Он внутренне накапливает данные для достижения высокой степени сжатия. И это нормально! Каждый раз, когда deflate() производит сжатый вывод, этот вывод добавляется к полю базы данных - медленный процесс.

Однако, как только deflate() производит сжатые данные, эти данные могут не поместиться в предоставленный выходной буфер deflate_out. Поэтому требуется несколько вызовов deflate(). И вот чего я хочу избежать:

Есть ли способ сделать deflate_out всегда достаточно большим, чтобы deflate() мог хранить в нем все сжатые данные каждый раз, когда он решает произвести вывод?

Примечания:

  • Общий размер несжатых данных заранее неизвестен. Как упоминалось выше, несжатые данные поступают фрагментами, а сжатые данные добавляются к полю базы данных, также фрагментами.

  • Во включаемом файле 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.
    */
    

person feklee    schedule 17.01.2012    source источник


Ответы (2)


deflateBound () полезен только в том случае, если вы выполняете все сжатие за один шаг или если вы принудительно вызываете deflate, чтобы сжать все входные данные, доступные ему в настоящее время, и выдать сжатые данные для всего этого входа. Это можно сделать с помощью параметра сброса, такого как Z_BLOCK, Z_PARTIAL_FLUSH и т. Д.

Если вы хотите использовать Z_NO_FLUSH, тогда становится намного сложнее и неэффективнее пытаться предсказать наибольший объем вывода, который deflate () может выдать при следующем вызове. Вы не знаете, какая часть входных данных была использована в момент передачи последнего пакета сжатых данных, поэтому вам нужно предполагать, что почти ничего из этого не было, а размер буфера увеличивался излишне. Как бы вы ни пытались оценить максимальный выход, вы без уважительной причины будете делать много ненужных маллокирований или повторных блоков, что неэффективно.

Нет смысла избегать вызова deflate () для получения дополнительных результатов. Если вы просто зацикливаете deflate () до тех пор, пока он не перестанет выводить вас, вы можете использовать фиксированный выходной буфер, созданный с ошибкой один раз. Именно так были разработаны интерфейсы deflate () и inflate (). Вы можете посмотреть на http://zlib.net/zlib_how.html хорошо документированный пример использования интерфейса.

Кстати, в последней версии zlib (1.2.6) есть функция deflatePending (), которая позволяет узнать, сколько выходных данных deflate () ожидает доставки.

person Mark Adler    schedule 31.01.2012
comment
Большое спасибо за подробный ответ! Чтобы предсказать требуемый выходной буфер для следующего вызова deflate(), я подумал о добавлении размера, сообщаемого deflatePending(), и значения, возвращаемого deflateBound(). Это похоже на предложение @EugenRieck. Однако, насколько я понимаю, это не очень хорошая идея, поскольку deflateBound() задокументирован для работы только при передаче размера всего ввода, подлежащего сжатию. Т.е. deflateBound() не задокументирован для работы с порциями ввода. - person feklee; 01.02.2012
comment
deflateBound () может работать для фрагментов ввода, но только в том случае, если весь предыдущий ввод был сжат и выдан. Это может быть обеспечено только путем использования опции сброса, отличной от Z_NO_FLUSH, и использования всего вывода на предыдущих вызовах. В этом случае deflatePending () будет полезен, когда используются Z_BLOCK или Z_PARTIAL_FLUSH, поскольку они могут оставить несколько битов позади. При использовании Z_NO_FLUSH в deflateBound () + deflatePending () будет отсутствовать третья часть, которая представляет собой размер входных данных, использованных при предыдущих вызовах deflate (), но еще не сжатых и отправленных. - person Mark Adler; 05.02.2012

Глядя на источники в поисках подсказки, я упал

/* =========================================================================
 * 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.

person Eugen Rieck    schedule 18.01.2012
comment
Вы были правы в комментарии к моему теперь удаленному ответу. Забывала о внутреннем состоянии. Из любопытства я посмотрел на это ожидающее значение после первого вызова, чтобы сдуть в быстром тесте. Значение avail_in было равно нулю, avail_out было 2, а отложенное - нулю (0). Похоже, он не отражает фактический объем ожидающих данных. Следующий вызов deflate для сброса сбросил на выходе ~ 8K. Так что это может быть неточное измерение ... по крайней мере, в одной ситуации. - person Mark Wilkins; 18.01.2012
comment
Вы говорите, что strm->state->pending - это размер данных, все еще находящихся в конвейере. Если я правильно понимаю, то этот размер увеличивается с каждым вызовом deflate(), пока не будет достигнута неизвестная верхняя граница. И эта верхняя граница как раз то, что я ищу. Так чем это полезно? Я что-то упускаю? - person feklee; 19.01.2012
comment
Я имел в виду, что если вы дадите deflate () буфер размера strm- ›state-› pending + deflateBound (strm, strm- ›avail_in), он никогда не исчерпает буферное пространство. - person Eugen Rieck; 19.01.2012
comment
Понятно. Поэтому перед вызовом deflate() нужно выделить strm->state->pending + deflateBound(strm, strm->avail_in) байта памяти для strm->next_out. Спасибо, что разобрались в этом! Тем не менее, я не уверен, стоит ли полагаться на этот метод. В конце концов, это не задокументировано как часть zlib API. - person feklee; 30.01.2012
comment
Документация в zlib.h настоятельно предполагает, что доступ к strm->state - плохая идея: struct internal_state FAR *state; /* not visible by applications */ - person feklee; 30.01.2012