Почему библиотеки Delphi zlib и zip настолько медленны до 64 бит?

Во время тестирования реального приложения я столкнулся с удивительной характеристикой производительности, связанной с библиотеками zlib и zip, которые поставляются с Delphi.

My real-world приложение экспортирует файлы .xlsx. Этот формат файла представляет собой набор файлов XML, завернутых в файл контейнера ZIP. Экспортный код .xlsx генерирует файлы XML, а затем передает их в ZIP-архив Delphi. Как только я оптимизировал генерацию файла XML до такой степени, что создание ZIP было узким местом, я, к моему удивлению, обнаружил, что 64-битный код был значительно медленнее 32-битного кода.

Чтобы изучить это, я создал эту тестовую программу:

program zlib_perf;

{$APPTYPE CONSOLE}

uses
  System.SysUtils, System.Classes, System.Diagnostics, System.Zip;

const
  LoremIpsum =
    'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod '+
    'tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, '+
    'quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo '+
    'consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse '+
    'cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat '+
    'non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';

function GetTestStream: TStream;
var
  Bytes: TBytes;
begin
  Result := TMemoryStream.Create;
  // fill the stream with 500MB of lorem ipsum
  Bytes := TEncoding.UTF8.GetBytes(LoremIpsum);
  while Result.Size < 500*1024*1024 do
    Result.WriteBuffer(Pointer(Bytes)^, Length(Bytes));
end;

procedure DoTest;
var
  DataStream, ZipStream: TStream;
  Stopwatch: TStopwatch;
  Zip: TZipFile;
begin
  DataStream := GetTestStream;
  try
    ZipStream := TMemoryStream.Create;
    try
      Zip := TZipFile.Create;
      try
        Zip.Open(ZipStream, zmWrite);

        Stopwatch := TStopwatch.StartNew;
        DataStream.Position := 0;
        Zip.Add(DataStream, 'foo');
        Writeln(Stopwatch.ElapsedMilliseconds);
      finally
        Zip.Free;
      end;
    finally
      ZipStream.Free;
    end;
  finally
    DataStream.Free;
  end;
end;

begin
  DoTest;
end.

Я скомпилировал программу как для XE2, так и для XE7, для 32 и 64 бит, а также для параметров компилятора конфигурации по умолчанию. Моя тестовая машина работает под управлением Windows 7 x64 на Intel Xeon E5530.

Вот результаты:

Compiler  Target  Time (ms)
     XE2   Win32       8586
     XE2   Win64      18908
     XE7   Win32       8583
     XE7   Win64      19304

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

Поскольку алгоритм сжатия, используемый вышеуказанным кодом, является zlib (индексный код Delphi поддерживает только сохранение и дефляцию), я считаю, что библиотека zlib, используемая Delphi, лежит в основе этой проблемы. Почему библиотека Delphi zlib настолько медленная до 64 бит?

Ответ 1

Как уже отмечалось, код сжатия ZIP для Delphi стоит поверх zlib. Реализация zlib в Delphi является оберткой вокруг официального исходного кода zlib C. Код C скомпилирован для объектов, а затем связан с {$LINK}. Для XE7 комментарии в верхней части System.ZLib указывают, что использовался zlib 1.2.8.

При очевидном предположении, что время расходуется внутри zlib-кода, наиболее правдоподобным объяснением поведения является то, что 64-битные скомпилированные объекты несут ответственность за низкую производительность. Либо используемый компилятор испускает слабый код, либо используется плохой выбор параметров компилятора.

Итак, я сделал следующие шаги:

  • Я загрузил источник для zlib 1.2.8 и скомпилирован с компилятором Microsoft 64 бит, cl.
  • Использование компилятора VS2010, версия 16.00.30319.01. Я скомпилировал объекты со следующими параметрами: /O2 /GS-.
  • Затем я взял копию System.ZLib.pas и включил ее в свой проект вместе с недавно скомпилированными объектами. Это гарантирует, что используются только что скомпилированные объекты zlib.
  • Я скомпилировал программу Delphi с XE7 для 64 бит.

Время выполнения на том же компьютере, что и для генерации данных в вопросе, составляло 6,912 мс.

Затем я перекомпилировал и опустил параметр /O2 и снова обвел цикл. На этот раз время работы составляло 20 077 мс. Поэтому я полагаю, что Embarcadero просто забыл скомпилировать эти объекты с оптимизацией.

Я опубликовал эту проблему на портале качества Embarcadero: https://quality.embarcadero.com/browse/RSP-9891

Как упоминалось в комментарии ниже, представляется вполне правдоподобным, что другие библиотеки, которые полагаются на скомпилированные объекты, могут иметь схожие проблемы. Возможные проблемные области:

  • MidasLib, объекты, вероятно, не критичны по производительности.
  • Indy, версия, поставляемая с Delphi, использует те же объекты zlib, которые я считаю.
  • System.RegularExpressions, оболочка вокруг PCRE.
  • Vcl.Imaging.jpeg, построенный на основе сторонней реализации JPEG, которая связана как скомпилированные объекты.

Обновление

Проблема с качественным порталом сообщает, что эта проблема была исправлена ​​в XE8.