Любой способ ускорить SaveToStream на TPNGImage?

У меня есть функция, которая преобразует TBitmap (который я рисую) в TPngImage, а затем сохраняет его в поток, чтобы другие методы могли использовать его. Png используется потому, что создает меньшие изображения для вывода отчета (Excel, HTML). Проблема в том, что SaveToStream, кажется, занимает слишком много времени, в 15 раз больше, чем преобразование TBitmap в TPngImage или использование TStream с png. Вот код:

var
 BitmapImage: TBitmap;      
 PNGImage: TPngImage;
 PngStream: TStream;        
begin
  // draw on BitmapImage
  ...
  PNGImage := TPngImage.Create;
  PNGStream := TMemoryStream.Create;
  Try
     PNGImage.Assign(BitmapPicture.Bitmap); // Step 1: assign TBitmap to PNG
     PNGImage.SaveToStream(PNGStream);  // Step 2: save PNG to stream
     WS.Shapes.AddPicture(PNGStream,PNGImage.Width,PNGImage.Height); // Step 3: Add PNG from Stream to Excel
  finally
     PNGImage.Free;
     PNGStream.Free;
  end;
...

Это проверено с 70000 изображений, и вот время:
Шаг 1: 7 с

Шаг 2: 93 с

Шаг 3: 6 с

Почему сохранение в Stream так медленно? Любое предложение, чтобы оптимизировать это?

Использование Delphi XE7

РЕДАКТИРОВАТЬ

Вот пример (MCVE) с простым bmp, который преобразуется в PNG и затем сохраняется в поток. Просто для другой проверки я добавил SaveToFile, который, конечно, занимает больше времени, но сохраняет на диск, поэтому я считаю приемлемым.

Размер файла img1.bmp составляет 49,5 КБ, а сохраненного PNG - 661 байт. ссылка на img1.bmp = http://www.filedropper.com/img1_1

TMemoryStreamAccess = class(TMemoryStream)
  end;

procedure TForm1.Button1Click(Sender: TObject);
var BitmapImage:TBitmap;
  PNGImage:TPngImage;
  PNGStream:TMemoryStream;//TStream;
  i,t1,t2,t3,t4,t5,t6: Integer;
  vFileName:string;
begin

  BitmapImage:=TBitmap.Create;
  BitmapImage.LoadFromFile('c:\tmp\img1.bmp');

  t1:=0; t2:=0; t3:=0; t4:=0; t5:=0; t6:=0;

  for i := 1 to 70000 do
  begin

    PNGImage:=TPngImage.Create;
    PNGStream:=TMemoryStream.Create;
    try

      t1:=GetTickCount;
      PNGImage.Assign(BitmapImage);
      t2:=t2+GetTickCount-t1;

      t3:=GetTickCount;
      TMemoryStreamAccess(PNGStream).Capacity := 1000;
      PNGImage.SaveToStream(PNGStream);
      // BitmapImage.SaveToStream(PNGStream); <-- very fast!
      t4:=t4+GetTickCount-t3;

    finally
      PNGImage.Free;
      PNGstream.Free
    end;

  end;

   showmessage('Assign = '+inttostr(t2)+' - SaveToStream = '+inttostr(t4));
end;

3 ответа

Решение

Это проверено с 70000 изображений, и вот время:

Шаг 1: 7 с

Шаг 2: 93 с

Шаг 3: 6 с

Почему сохранение в Stream так медленно?

Давайте разберемся с некоторыми цифрами:

Шаг 1: 7 с = 7000 мс. 7000 / 70000 = 0,1 мс на изображение

Шаг 2: 93 с = 93000 мс. 93000 / 70000 = ~1,33 мс на изображение

Шаг 3: 6 с = 6000 мс. 6000 / 70000 = ~0,086мс на изображение

Как вы думаете, 1,33 мс за SaveToStream() медленный? Вы просто делаете МНОГО из них, так что они со временем складываются, вот и все.

При этом данные PNG в памяти не сжимаются. Это сжато, когда данные сохранены. Так что это одна из причин замедления. Кроме того, сохранение PNG выполняет много операций записи в поток, что может привести к тому, что поток выполнит многократное (повторное) выделение памяти (TPNGImage также выполняет распределение внутренней памяти во время сохранения), так что это еще одно замедление.

Любое предложение, чтобы оптимизировать это?

Вы ничего не можете сделать с накладными расходами на сжатие, но вы можете, по крайней мере, предварительно установить TMemoryStream.Capacity до разумного значения перед звонком SaveToStream() уменьшить перераспределение памяти, которое TMemoryStream необходимо выполнить во время записи. Вам не нужно быть точным с этим. Если запись в поток вызывает его Size превысить свой текущий Capacity, это просто увеличит его Capacity соответственно. Поскольку вы уже обработали 70000 изображений, возьмите их средний размер и добавьте к ним еще несколько КБ, и используйте их в качестве исходного Capacity,

type
  TMemoryStreamAccess = class(TMemoryStream)
  end;

var
  BitmapImage: TBitmap;      
  PNGImage: TPngImage;
  PngStream: TMemoryStream;        
begin
  // draw on BitmapImage
  ...
  PNGImage := TPngImage.Create;
  Try
    PNGImage.Assign(BitmapPicture.Bitmap); // Step 1: assign TBitmap to PNG
    PNGStream := TMemoryStream.Create;
    try
      TMemoryStreamAccess(PNGStream).Capacity := ...; // some reasonable value
      PNGImage.SaveToStream(PNGStream);  // Step 2: save PNG to stream
      WS.Shapes.AddPicture(PNGStream, PNGImage.Width, PNGImage.Height); // Step 3: Add PNG from Stream to Excel
    finally
      PNGStream.Free;
    end;
  finally
    PNGImage.Free;
  end;
  ...

Если это все еще недостаточно быстро, рассмотрите возможность использования потоков для параллельной обработки нескольких изображений. Не обрабатывайте их последовательно.

Вы назначали уровень сжатия? Я не заметил ничего подобного

PNGImage.CompressionLevel := 1;

в вашем коде. Он может быть в диапазоне от 0 до 9. По умолчанию это 7. Если вы установите его в 1, это будет значительно быстрее, в то время как увеличение размера выходного потока будет незначительным.

Возможно, это не связано напрямую, но у меня была проблема с чтением/записью (в ОЗУ), потому что я использовалTMemoryStream(байт за байтом).

В моем случае я мог бы адаптировать свой код для работы в чистой оперативной памяти сSetLength(MyEncripted,TheSize);&SetLength(MyClear,TheSize);вместоTMemoryStream.ReadиTMemoryStream.Write.

То, что я делал, было простой «концепцией» (которую можно сделать с чистыми строковыми переменными RAM)MyEncripted[i]:=Chr(Ord(MyClear[i]) xor Ord(MyKey[i]));с TMemoryStream.Write в байтовой логике.

Мои измеренные тайминги:

  • Использование TMemoryStream для 633 килобайт => 3'21"
  • Использование переменных типа RAM String для 633 килобайт => Почти мгновенное, менее 0,1 дюйма
  • Использование TMemoryStream для 6,3 мегабайт => 33'30"
  • Использование переменных типа RAM String для 6,3 мегабайт => Почти мгновенное, менее 0,1 дюйма
  • Использование TMemoryStream для 63 мегабайт => 5ч35'00"
  • Использование переменных типа RAM String для 63 мегабайт => почти мгновенное, менее 0,1 дюйма
  • Использование TMemoryStream для 633 мегабайт => 55ч50'00"
  • Использование переменных типа RAM String для 633 мегабайт => менее 1 дюйма

Примечание: я не стал углубляться в тайминги, так как 55 часов действительно огромны и их было достаточно, чтобы увидеть, что происходит; но похоже, что он масштабируется «линейно».

TMemoryStream слишком медленный , настолько медленный, что то, что можно сделать в ОЗУ, занимающее менее ОДНОЙ секунды, может занять более ДВУХ дней при использовании TMemoryStream.Write (побайтовая логика).

Поэтому я давно решил никогда больше не использовать TMemoryStream.

Надеюсь, это поможет понять, какой уровень SLOW у TMemoryStream по сравнению с прямыми строковыми переменными RAM.

Другие вопросы по тегам