Любой способ ускорить 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.