Есть ли способ ускорить SaveToStream на TPNGImage?
У меня есть функция, которая преобразует TBitmap (который я рисую) в TPngImage, а затем сохраняет его в stream, чтобы другие методы могли его использовать. 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 s
Шаг 2: 93 s
Шаг 3: 6 s
Почему сохранение потока так медленно? Есть предложения, как это оптимизировать?
Использование Delphi XE7
EDIT
Вот пример (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:tmpimg1.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;
2 ответа:
Это тестируется с 70000 изображений и вот тайминги:
Шаг 1: 7 s
Шаг 2: 93 s
Шаг 3: 6 s
Почему сохранение потока так медленно?
Давайте проверим некоторые числа:
Шаг 1: 7С = 7000ms. 7000 / 70000 = 0.1 МС на изображения
Шаг 2: 93s = 93000ms. 93000 / 70000 = ~1.33 МС на изображение Шаг 3: 6s = 6000ms. 6000 / 70000 = ~0.086 МС на изображениеКак вы думаете, 1,33 МС per
SaveToStream()
- это медленно? Вы просто делаете их много, так что они складываются со временем, вот и все.При этом данные PNG в памяти не сжимаются. Он сжимается, когда данные сохраняются. Так что это одна из причин замедления. Кроме того, сохранение PNG делает много записей в поток, что может привести к тому, что поток выполнит многократное выделение памяти (re) (
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, это будет значительно быстрее, в то время как увеличение размера выходного потока будет незначительным.