Добавление файлов в существующую проблему с Zip-производительностью

У меня есть веб-сервис WCF, который сохраняет файлы в папку (около 200 000 небольших файлов). После этого мне нужно переместить их на другой сервер.

Решение, которое я нашел, это закрепить их, а затем переместить их.

Когда я принял это решение, я провел тест с (20 000 файлов), замачивая 20 000 файлов, заняв всего около 2 минут, и перемещение zip происходит очень быстро. Но в производстве, застежка 200 000 файлов занимает более 2 часов.

Вот мой код для zip-папки:

using (ZipFile zipFile = new ZipFile())
{
    zipFile.UseZip64WhenSaving = Zip64Option.Always;
    zipFile.CompressionLevel = CompressionLevel.None;
    zipFile.AddDirectory(this.SourceDirectory.FullName, string.Empty);

    zipFile.Save(DestinationCurrentFileInfo.FullName);
}

Я хочу изменить веб-сервис WCF, чтобы вместо сохранения в папку он сохранялся в zip.

Я использую следующий код для тестирования:

var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".aes")).Select(f => new FileInfo(f));

foreach (var additionFile in listAes)
{
    using (var zip = ZipFile.Read(nameOfExistingZip))
    {
        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
        zip.AddFile(additionFile.FullName);

        zip.Save();
    }

    file.WriteLine("Delay for adding a file  : " + sw.Elapsed.TotalMilliseconds);
    sw.Restart();
}

Первый файл для добавления в zip занимает всего 5 мс, но добавленный 10 000-й файл занимает 800 мс.

Есть ли способ оптимизировать это? Или если у вас есть другие предложения?

ИЗМЕНИТЬ

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

Ответы

Ответ 1

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

Рассуждение

Теперь, когда мы все на одной странице о том, как они работают, позвольте начать рассуждать, разбив, как это работает, используя исходный код; мы продолжим оттуда вперед:

var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories).Where(s => s.EndsWith(".aes")).Select(f => new FileInfo(f));

foreach (var additionFile in listAes)
{
    // (1)
    using (var zip = ZipFile.Read(nameOfExistingZip))
    {
        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
        // (2)
        zip.AddFile(additionFile.FullName);

        // (3)
        zip.Save();
    }

    file.WriteLine("Delay for adding a file  : " + sw.Elapsed.TotalMilliseconds);
    sw.Restart();
}
  • (1) открывает ZIP файл. Вы делаете это для каждого файла, который вы пытаетесь добавить
  • (2) Добавляет один файл в ZIP файл
  • (3) Сохраняет полный ZIP файл

На моем компьютере это занимает около часа.

Теперь не все данные формата файла актуальны. Мы ищем материал, который будет все хуже в вашей программе.

Сокращаясь по спецификации формата файла, вы заметите, что сжатие основано на Deflate, которое не требует информации о других сжатых файлах. Двигаясь дальше, мы заметим, как "таблица файлов" хранится в ZIP файле:

Zip file structure

Вы заметите, что там есть "центральный каталог", который хранит файлы в ZIP файле. Он в основном хранится как "список". Таким образом, используя эту информацию, мы можем объяснить, что тривиальный способ обновить ее при реализации шагов (1-3) в следующем порядке:

  • Откройте zip файл, прочитайте центральный каталог
  • Добавить данные для (нового) сжатого файла, сохранить указатель вместе с именем файла в новом центральном каталоге.
  • Перезапишите центральный каталог.

Подумайте об этом на мгновение, для файла № 1 вам потребуется 1 операция записи; для файла # 2 вам необходимо прочитать (1 элемент), добавить (в память) и написать (2 элемента); для файла № 3 вам необходимо прочитать (2 элемента), добавить (в память) и написать (3 элемента). И так далее. Это означает, что производительность будет снижаться, если вы добавите больше файлов. Вы уже это заметили, теперь вы знаете, почему.

Возможное решение

В предыдущем решении я добавил сразу все файлы. Это может не работать в вашем случае использования. Другим решением является реализация слияния, которое в каждом случае объединяет 2 файла. Это более удобно, если у вас нет всех файлов, доступных при запуске процесса сжатия.

В основном алгоритм становится следующим:

  • Добавьте несколько (скажем, 16 файлов). Вы можете играть с этим номером. Сохраните это в файле -say- 'file16.zip'.
  • Добавьте больше файлов. Когда вы нажимаете 16 файлов, вы должны объединить два файла из 16 элементов в один файл из 32 элементов.
  • Объединить файлы, пока вы больше не сможете их объединить. В основном каждый раз, когда у вас есть два файла из N элементов, вы создаете новый файл из 2 * N элементов.
  • Перейти (2).

Опять же, мы можем рассуждать об этом. Первые 16 файлов не проблема, мы уже установили это.

Мы также можем рассуждать о том, что произойдет в нашей программе. Поскольку мы объединяем 2 файла в 1 файл, нам не нужно делать столько чтения и записи. На самом деле, если вы рассудите об этом, вы увидите, что у вас есть файл из 32 записей в 2 слияния, 64 в 4 слияния, 128 в 8 слияниях, 256 из 16 слияний... эй, подождите, мы знаем эту последовательность, это 2^N. Опять же, рассуждая об этом, мы обнаружим, что нам нужно около 500 слияний, что намного лучше, чем 200 000 операций, с которых мы начали.

Взлом в ZIP-архиве

Еще одно решение, которое может прийти на ум, - это комбинировать центральный каталог, создавая свободное место для будущих записей. Тем не менее, это, вероятно, требует, чтобы вы взломали почтовый индекс и создали свой собственный файл ZIP-записи. Идея состоит в том, что вы в основном зацикливаете центральную директорию на 200K записей, прежде чем начать, чтобы вы могли просто добавить на место.

Опять же, мы можем рассуждать об этом: добавление файла теперь означает: добавление файла и обновление некоторых заголовков. Это будет не так быстро, как исходное решение, потому что вам понадобится случайный диск IO, но он, вероятно, будет работать достаточно быстро.

Я не справился с этим, но мне это не кажется слишком сложным.

Самое простое решение - наиболее практичный

То, что мы не обсуждали до сих пор, - это самое простое решение: один подход, который приходит на ум, - просто добавить все файлы сразу, о чем мы снова можем рассуждать.

Реализация довольно проста, потому что теперь нам не нужно делать какие-либо причудливые вещи; мы можем просто использовать обработчик ZIP (я использую ионный) как есть:

static void Main()
{
    try { File.Delete(@"c:\tmp\test.zip"); }
    catch { }

    var sw = Stopwatch.StartNew();

    using (var zip = new ZipFile(@"c:\tmp\test.zip"))
    {
        zip.UseZip64WhenSaving = Zip64Option.Always;
        for (int i = 0; i < 200000; ++i)
        {
            string filename = "foo" + i.ToString() + ".txt";
            byte[] contents = Encoding.UTF8.GetBytes("Hello world!");
            zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
            zip.AddEntry(filename, contents);
        }

        zip.Save();
    }

    Console.WriteLine("Elapsed: {0:0.00}s", sw.Elapsed.TotalSeconds);
    Console.ReadLine();
}

одолевать; который заканчивается через 4,5 секунды. Гораздо лучше.

Ответ 2

Я вижу, что вы просто хотите сгруппировать 200 000 файлов в один большой одиночный файл без сжатия (например, архив tar). Две идеи для изучения:

  • Экспериментируйте с другими форматами файлов, чем Zip, поскольку это может быть не самый быстрый. tar (ленточный архив) приходит на ум (с естественными преимуществами скорости благодаря своей простоте), он даже имеет append mode, который является именно тем, что вы делаете после выполнения операций O (1). SharpCompress - это библиотека, которая позволит вам работать с этим форматом (и другими).

  • Если у вас есть контроль над удаленным сервером, вы можете реализовать свой собственный формат файла, самым простым, о котором я могу думать, было бы поменять каждый новый файл отдельно (для хранения метаданных файла, таких как имя, дата и т.д. в самом содержимом файла), а затем добавить каждый такой файл в один файл необработанного байта. Вам просто нужно сохранить смещения байта (разделенные столбцами в другом txt файле), чтобы позволить удаленному серверу разбить огромный файл на 200 000 ZIP файлов, а затем разархивировать каждый из них, чтобы получить метаданные. Думаю, это тоже примерно то, что делает за сценой:).

  • Пробовали ли вы zipping для MemoryStream, а не для файла, только для очистки файла, когда вы закончили день? Конечно, для резервных целей ваша служба WCF должна будет хранить копию полученных отдельных файлов, пока вы не убедитесь, что они были "переданы" удаленному серверу.

  • Если вам нужно сжатие, 7-Zip (и возиться с параметрами) стоит попробовать.

Ответ 3

Вы открываете файл повторно, почему бы не добавить цикл и не добавить все в один почтовый индекс, а затем сохранить его?

var listAes = Directory.EnumerateFiles(myFolder, "*.*", SearchOption.AllDirectories)
    .Where(s => s.EndsWith(".aes"))
    .Select(f => new FileInfo(f));

using (var zip = ZipFile.Read(nameOfExistingZip))
{
    foreach (var additionFile in listAes)
    {
        zip.CompressionLevel = Ionic.Zlib.CompressionLevel.None;
        zip.AddFile(additionFile.FullName);
    }
    zip.Save();
}

Если файлы не все доступны сразу, вы можете, по крайней мере, объединить их вместе. Поэтому, если вы ожидаете 200k файлов, но вы только получили 10 до сих пор, не открывайте почтовый индекс, добавляйте его, а затем закрывайте. Подождите еще несколько шагов и добавьте их в пакеты.

Ответ 4

Если вы в порядке с производительностью 100 * 20 000 файлов, не можете ли вы просто разбить свой большой ZIP файл на 100 "маленьких" ZIP файлов? Для простоты создайте новый ZIP файл каждую минуту и ​​поместите отметку времени в название.

Ответ 5

Вы можете заархивировать все файлы с помощью .Net TPL (параллельной библиотеки задач) следующим образом:

    while(0 != (read = sourceStream.Read(bufferRead, 0, sliceBytes)))
{
   tasks[taskCounter] = Task.Factory.StartNew(() => 
     CompressStreamP(bufferRead, read, taskCounter, ref listOfMemStream, eventSignal)); // Line 1
   eventSignal.WaitOne(-1);           // Line 2
   taskCounter++;                     // Line 3
   bufferRead = new byte[sliceBytes]; // Line 4
}

Task.WaitAll(tasks);                  // Line 6

Здесь есть скомпилированная библиотека и исходный код:

http://www.codeproject.com/Articles/49264/Parallel-fast-compression-unleashing-the-power-of