Написать консольную программу на C#, предназначенную для поблочного сжатия и расжатия файлов с помощью System.IO.Compression.GzipStream.
Для компрессии исходный файл делится на блоки одинакового размера, например, в 1 мегабайт. Каждый блок компрессится и записывается в выходной файл независимо от остальных блоков.
Программа должна эффективно распараллеливать и синхронизировать обработку блоков в многопроцессорной среде и уметь обрабатывать файлы, размер которых превышает объем доступной оперативной памяти.
В случае исключительных ситуаций необходимо проинформировать пользователя понятным сообщением, позволяющим пользователю исправить возникшую проблему, в частности если проблемы связаны с ограничениями операционной системы. При работе с потоками допускается использовать только стандартные классы и библиотеки из .Net 3.5 (исключая ThreadPool, BackgroundWorker, TPL). Ожидается реализация с использованием Thread-ов.
Параметры программы, имена исходного и результирующего файлов должны задаваться в командной строке следующим образом:
GZipTest.exe compress/decompress [имя исходного файла] [имя результирующего файла]
Основной алгоритм компресии/декомпрессии находится в классе Pipeline. Класс создает несколько потоков (по количеству процессоров) для трансформации (компрессия или декомпрессия) и по одному потоку на чтение и запись в stream.
- Читающий поток читает из исходного файла данные поблочно и складывает блоки в очередь с ограничениями для трансформации.
- Потоки трансформации ждут когда в очереди появится блок для трансформации, выбирают его, трансформируют (сжимают или расжимают) и складывают результат в очередь (тоже с ограничением) на запись.
- Записывающий поток вытаскивает блоки из своей очереди и записывает их в результирующий файл.
Если в очереди лежит слишком много элементов, то попытка добавления элемента заблокирует поток до того момента, пока другой поток не заберет из очереди данные. При попытке забрать из пустой очереди данные приведет к блокированию потока до того момента, пока в очереди не появятся данные.
За идею взята System.Collection.Concurrent.BlockingCollection. Реализована с помощью семафоров.
В качестве основной очереди - реализована потокобезопасная очередь без блокировок потока (Interlocked.CompareExchange). Тесты показали, что очередь с обычными lock будет работать примерно так-же (практически отсутствует lock contention), но решил не убирать эту реализацию.
Для эффективной работы с памятью стараюсь не создавать много объектов, переиспользовать буфера при копировании данных из одного stream в другой. Для этого написан BufferPool, самая простая реализация (по хорошему надо использовать ArrayPool).
Для эффективной работы с потоковыми данными в памяти используется RecyclableMemoryStream (сторонний nuget пакет). Он наследник MemoryStream, внутри себя он может использовать несколько массивов для хранения данных, лишний раз не копируя их.
- Chaos monkey tests
- Sinthetic benchmarks (what if disk is slow or CPU or low memory)
- Reference implementation via MS Pipeline API