Есть ли способ ускорить 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, это будет значительно быстрее, в то время как увеличение размера выходного потока будет незначительным.