Это действительно интересный вопрос.Сжатие сильно загружает процессор, полагаясь на множество поисков и сравнений.Поэтому очень уместно хотеть распараллелить это, когда у вас есть несколько процессоров с беспрепятственным доступом к памяти.
В библиотеке DotNetZip есть класс ParallelDeflateOutputStream
, который делает то, что вы описываете.Класс задокументирован здесь .
Он может использоваться только для сжатия - без декомпрессии.Также это строго выходной поток - вы не можете read
сжать.Учитывая эти ограничения, это в основном DeflateOutputStream, который внутренне использует несколько потоков.
Как это работает: он разбивает входящий поток на куски, а затем сбрасывает каждый кусок в отдельный рабочий поток для сжатия по отдельности.Затем он объединяет все эти сжатые потоки обратно в один упорядоченный поток в конце.
Предположим, что размер "порции", поддерживаемый потоком, составляет N байтов.Когда вызывающая сторона вызывает Write (), данные помещаются в буфер или порцию.Внутри метода Stream.Write()
, когда первый «сегмент» заполнен, он вызывает ThreadPool.QueueUserWorkItem
, выделяя сегмент для рабочего элемента.Последующие записи в поток начинают заполнять следующий сегмент, и когда , что заполнено, Stream.Write()
снова вызывает QUWI
.Каждый рабочий поток сжимает свой сегмент, используя «тип сброса» Sync
(см. Спецификацию deflate), а затем отмечает свой сжатый большой двоичный объект, готовый к выводу.Эти различные выходные данные затем переупорядочиваются (поскольку фрагмент n не обязательно сжимается перед фрагментом n + 1) и записываются в поток выходных данных.Когда написано каждое ведро, оно помечается пустым и готово для повторного заполнения следующим Stream.Write()
.Каждый блок должен быть сжат с помощью типа синхронизации «Синхронизация», чтобы разрешить их повторную комбинацию посредством простой конкатенации, чтобы объединенный поток данных был легальным потоком DEFLATE.Конечный кусок должен Тип сброса = Готово.
Конструкция этого потока означает, что вызывающим не нужно писать с несколькими потоками. Вызывающие просто создают поток как обычно, как ванильный DeflateStream, используемый для вывода, и записывают в него,Потоковый объект использует несколько потоков, но ваш код не взаимодействует напрямую с ними.Код для «пользователя» ParallelDeflateOutputStream
выглядит следующим образом:
using (FileStream raw = new FileStream(CompressedFile, FileMode.Create))
{
using (FileStream input = File.OpenRead(FileToCompress))
{
using (var compressor = new Ionic.Zlib.ParallelDeflateOutputStream(raw))
{
// could tweak params of parallel deflater here
int n;
var buffer = new byte[8192];
while ((n = input.Read(buffer, 0, buffer.Length)) != 0)
{
compressor.Write(buffer, 0, n);
}
}
}
}
Он был разработан для использования в классе DotNetZip ZipFile, но он вполне пригоден для использования в качестве автономного сжатия выходного потока.Результирующий поток может быть ДЕДЕЛЬФАТИРОВАН (раздуваться?) Любым инфлятором.Результат полностью соответствует спецификации.
Поток настраивается.Вы можете установить размер используемых им буферов и уровень параллелизма.Он не создает сегменты без ограничений, потому что для больших потоков (в гигабайтах и т. Д.) Это может привести к нехватке памяти.Таким образом, существует фиксированное ограничение на количество сегментов и, следовательно, степень параллелизма, которая может поддерживаться.
На моей двухъядерной машине этот класс потока почти удвоил скорость сжатия больших (100 МБ и более) файлов по сравнению со стандартным DeflateStream.У меня нет больших многоядерных машин, поэтому я не смог протестировать их дальше.Компромисс состоит в том, что параллельная реализация использует больше ЦП и больше памяти, а также сжимает немного менее эффективно (на 1% меньше для больших файлов) из-за синхронизации кадров, которую я описал выше.Преимущество в производительности будет зависеть от пропускной способности ввода-вывода в потоке вывода и от того, сможет ли хранилище идти в ногу с потоками параллельного компрессора.
Предупреждение:
Это поток DEFLATE, а не GZIP.Для отличий прочитайте RFC 1951 (DEFLATE) и RFC 1952 (GZIP) .
Но если вам действительно нужен gzip, источник этого потока доступен, так что вы можете просмотреть его и, возможно, получить некоторые идеи для себя.GZIP - это просто оболочка над DEFLATE с некоторыми дополнительными метаданными (такими как контрольная сумма Адлера и т. Д. - см. Спецификацию).Мне кажется, что построить ParallelGzipOutputStream
будет не очень сложно, но, возможно, это тоже не тривиально.
Самой сложной задачей для меня было заставить семантику Flush () и Close () работать должным образом.
EDIT
Ради интереса я создал ParallelGZipOutputStream, который в основном выполняет то, что я описал выше, для GZip.Он использует задачи .NET 4.0 вместо QUWI для обработки параллельного сжатия.Я только что протестировал его на текстовом файле размером 100 МБ, созданном с помощью механизма цепей Маркова.Я сравнил результаты этого класса с некоторыми другими вариантами.Вот как это выглядит:
uncompressed: 104857600
running 2 cycles, 6 Flavors
System.IO.Compression.GZipStream: .NET 2.0 builtin
compressed: 47550941
ratio : 54.65%
Elapsed : 19.22s
ICSharpCode.SharpZipLib.GZip.GZipOutputStream: 0.86.0.518
compressed: 37894303
ratio : 63.86%
Elapsed : 36.43s
Ionic.Zlib.GZipStream: DotNetZip v1.9.1.5, CompLevel=Default
compressed: 37896198
ratio : 63.86%
Elapsed : 39.12s
Ionic.Zlib.GZipStream: DotNetZip v1.9.1.5, CompLevel=BestSpeed
compressed: 47204891
ratio : 54.98%
Elapsed : 15.19s
Ionic.Exploration.ParallelGZipOutputStream: DotNetZip v1.9.1.5, CompLevel=Default
compressed: 39524723
ratio : 62.31%
Elapsed : 20.98s
Ionic.Exploration.ParallelGZipOutputStream:DotNetZip v1.9.1.5, CompLevel=BestSpeed
compressed: 47937903
ratio : 54.28%
Elapsed : 9.42s
Выводы:
GZipStream, встроенный в .NET, довольно быстрый.Это также не очень эффективно и не настраивается.
"BestSpeed" на ванильном (непараллельном) GZipStream в DotNetZip примерно на 20% быстрее встроенного потока .NETи дает примерно такое же сжатие.
Использование нескольких задач для сжатия может сократить до 45% времени, необходимого на моем двухъядерном ноутбуке (3 ГБ ОЗУ), сравнивая ванильный DotNetZip GZipStream с параллельным.Я предполагаю, что экономия времени будет выше для машин с большим количеством ядер.
Параллельно GZIP стоит платить - кадрирование увеличивает размер сжатого файла примерно на 4%.Это не изменится с количеством используемых ядер.
Полученный файл .gz может быть распакован любым инструментом GZIP.