Эффективность GZipStream

Я пытаюсь сохранить большой массив UInt16 в файл. positionCnt составляет около 50000, stationCnt составляет около 2500. Сохраненный напрямую, без GZipStream, файл имеет размер около 250 МБ, который может быть сжат внешней программой zip до 19 МБ. Со следующим кодом размер файла составляет 507 МБ. Что я делаю неправильно?

GZipStream cmp = new GZipStream(File.Open(cacheFileName, FileMode.Create), CompressionMode.Compress);
BinaryWriter fs = new BinaryWriter(cmp);
fs.Write((Int32)(positionCnt * stationCnt));
for (int p = 0; p < positionCnt; p++)
{
    for (int s = 0; s < stationCnt; s++)
    {
       fs.Write(BoundData[p, s]);
    }
}
fs.Close();

person danatel    schedule 28.09.2011    source источник
comment
Каково это, если вы сжимаете его используя gzip извне?   -  person Jon Skeet    schedule 28.09.2011
comment
внешний gzip дает около 19,5 Мб; bzip2 чуть меньше 8MB   -  person danatel    schedule 29.09.2011


Ответы (2)


Не знаете, на какой версии .NET вы работаете. В более ранних версиях использовался размер окна, равный размеру буфера, из которого вы записывали. Так что в вашем случае он попытается сжать каждое целое число по отдельности. Я думаю, что они изменили это в .NET 4.0, но не проверяли это.

В любом случае вам нужно создать буферизованный поток перед GZipStream:

// Создать файловый поток с буфером размером 64 КБ FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None, 65536); GZipStream cmp = новый GZipStream(fs, CompressionMode.Compress); ...

GZipStream cmp = new GZipStream(File.Open(cacheFileName, FileMode.Create), CompressionMode.Compress);
BufferedStream buffStrm = new BufferedStream(cmp, 65536);
BinaryWriter fs = new BinaryWriter(buffStrm);

Таким образом, GZipStream получает данные порциями по 64 Кбайт и может гораздо лучше сжимать их.

Буферы размером более 64 КБ не дадут лучшего сжатия.

person Jim Mischel    schedule 28.09.2011
comment
.Net 4, несжатый — 250 МБ, сжатый по 1 шорту за раз (независимо от буфера) — 411 МБ, сжатый 2500 шорт за раз — 165 МБ. - person user7116; 29.09.2011
comment
Спасибо за предложение. Но это не помогает. Результат с бОльшим буфером примерно такой же (517МБ - я тоже изменил содержимое массива для ускорения экспериментов). Также есть проблема с именем fs, которое вы использовали в своем примере — fs — это BinnaryFormatter (это моя ошибка, используемые мной имена fs и cmp сбивают с толку). - person danatel; 29.09.2011
comment
@danatel: моя ошибка. Я поставил буфер не на тот конец. Смотрите мое исправление, в котором используется BufferedStream. - person Jim Mischel; 29.09.2011
comment
Спасибо, помогло - результат с моими данными и 65к буфером 57Мб; при начальном содержимом (тот же UInt16 повторяется по буферу) 3МБ, с моими данными и буфером 20МБ получается 50МБ. - person danatel; 29.09.2011
comment
20-мегабайтный буфер сделал большое улучшение? Должно быть, они внесли серьезные изменения в .NET 4.0. Мне придется еще раз взглянуть на GZipStream. - person Jim Mischel; 29.09.2011
comment
Верна ли эта оптимизация (с использованием BufferedStream) и для декомпрессии? - person rolls; 08.02.2018
comment
@rolls да, буферизованный поток улучшит производительность декомпрессии. - person Jim Mischel; 08.02.2018
comment
Что делать, если массив байтов уже полностью находится в памяти? Тогда это не было бы преимуществом, не так ли? - person rolls; 10.02.2018
comment
@rolls Буферизованный поток повышает скорость записи на диск или чтения с него. Если вы работаете строго в памяти, то буферизованный поток, вероятно, замедлит вас. - person Jim Mischel; 12.02.2018

По какой-то причине, которая не была очевидна для меня во время быстрого чтения реализации GZip в .Net, производительность чувствительна к количеству данных, записываемых за один раз. Я сравнил ваш код с несколькими стилями записи на GZipStream и обнаружил, что наиболее эффективная версия записывает большие шаги на диск.

Компромиссом в этом случае является память, так как вам нужно преобразовать short[,] в byte[] в зависимости от желаемой длины шага:

using (var writer = new GZipStream(File.Create("compressed.gz"),
                                   CompressionMode.Compress))
{
    var bytes = new byte[data.GetLength(1) * 2];
    for (int ii = 0; ii < data.GetLength(0); ++ii)
    {
        Buffer.BlockCopy(data, bytes.Length * ii, bytes, 0, bytes.Length);
        writer.Write(bytes, 0, bytes.Length);
    }

    // Random data written to every other 4 shorts
    // 250,000,000 uncompressed.dat
    // 165,516,035 compressed.gz (1 row strides)
    // 411,033,852 compressed2.gz (your version)
}
person user7116    schedule 28.09.2011
comment
Благодарим за ваше предложение. Я не знаю, какое содержимое массива вы использовали для теста. Мой контент довольно регулярен и может быть сжат до 8 МБ. 165 МБ слишком много. - person danatel; 29.09.2011
comment
data[ii, jj] = random.Next() для половины данных (~125 МБ). Я просто указывал на различия в сжатии с использованием 1-кратного и 1-рядного за раз. - person user7116; 29.09.2011
comment
Это объясняет разницу - случайный шум не так сжимаем, как мои обычные данные. Спасибо за помощь. - person danatel; 29.09.2011