有什么方法可以加速 TPNGImage 上的 SaveToStream 吗?
Any way to speed up SaveToStream on TPNGImage?
我有一个函数可以将TBitmap(我画的)转换成TPngImage,然后保存到stream,这样其他方法就可以使用它了。使用 PNG 是因为它会为报告输出创建较小的图像 (excel、html)。问题是 SaveToStream 似乎花费了太多时间,比将 TBitmap 转换为 TPngImage 或将 TStream 与 png 一起使用要多 15 倍。这是代码:
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
编辑
这是带有简单 bmp 的示例 (MCVE),它被转换为 PNG,然后保存到流中。为了再次验证,我添加了 SaveToFile,这当然需要更长的时间,但它正在保存到磁盘,所以我认为可以接受。
img1.bmp是49.5KB,保存的PNG是661字节。
link 到 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;
This is tested with 70000 images and here are the timings:
Step 1: 7 s
Step 2: 93 s
Step 3: 6 s
Why is saving to Stream so slow?
让我们计算一些数字:
第 1 步:7 秒 = 7000 毫秒。 7000 / 70000 = 每张图片 0.1 毫秒
第 2 步:93 秒 = 93000 毫秒。 93000 / 70000 = 每张图片约 1.33 毫秒
第 3 步:6 秒 = 6000 毫秒。 6000 / 70000 = 每张图片约 0.086 毫秒
您认为每 SaveToStream()
1.33 毫秒很慢吗?你只是做了很多,所以它们会随着时间的推移而增加,仅此而已。
也就是说,内存中的 PNG 数据没有被压缩。保存数据时它会被压缩。所以这是放缓的原因之一。此外,保存 PNG 会对流进行大量写入,这可能会导致流执行多次内存(重新)分配(TPNGImage
也在保存期间执行内部内存分配),因此这是另一个减速。
Any suggestion to optimize this?
对于压缩开销您无能为力,但您至少可以在调用 SaveToStream()
之前将 TMemoryStream.Capacity
预先设置为合理的值,以减少 [=15] 的内存重新分配=] 需要在写入时执行。你不需要精确地对待它。如果写入流导致其 Size
超过其当前 Capacity
,它只会相应地增加其 Capacity
。由于您已经处理了 70000 张图像,取它们的平均大小并向其添加几 KB,并将其用作初始 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,它会明显更快,而输出流大小的增加可以忽略不计。
我有一个函数可以将TBitmap(我画的)转换成TPngImage,然后保存到stream,这样其他方法就可以使用它了。使用 PNG 是因为它会为报告输出创建较小的图像 (excel、html)。问题是 SaveToStream 似乎花费了太多时间,比将 TBitmap 转换为 TPngImage 或将 TStream 与 png 一起使用要多 15 倍。这是代码:
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
编辑
这是带有简单 bmp 的示例 (MCVE),它被转换为 PNG,然后保存到流中。为了再次验证,我添加了 SaveToFile,这当然需要更长的时间,但它正在保存到磁盘,所以我认为可以接受。
img1.bmp是49.5KB,保存的PNG是661字节。 link 到 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;
This is tested with 70000 images and here are the timings:
Step 1: 7 s
Step 2: 93 s
Step 3: 6 s
Why is saving to Stream so slow?
让我们计算一些数字:
第 1 步:7 秒 = 7000 毫秒。 7000 / 70000 = 每张图片 0.1 毫秒
第 2 步:93 秒 = 93000 毫秒。 93000 / 70000 = 每张图片约 1.33 毫秒
第 3 步:6 秒 = 6000 毫秒。 6000 / 70000 = 每张图片约 0.086 毫秒
您认为每 SaveToStream()
1.33 毫秒很慢吗?你只是做了很多,所以它们会随着时间的推移而增加,仅此而已。
也就是说,内存中的 PNG 数据没有被压缩。保存数据时它会被压缩。所以这是放缓的原因之一。此外,保存 PNG 会对流进行大量写入,这可能会导致流执行多次内存(重新)分配(TPNGImage
也在保存期间执行内部内存分配),因此这是另一个减速。
Any suggestion to optimize this?
对于压缩开销您无能为力,但您至少可以在调用 SaveToStream()
之前将 TMemoryStream.Capacity
预先设置为合理的值,以减少 [=15] 的内存重新分配=] 需要在写入时执行。你不需要精确地对待它。如果写入流导致其 Size
超过其当前 Capacity
,它只会相应地增加其 Capacity
。由于您已经处理了 70000 张图像,取它们的平均大小并向其添加几 KB,并将其用作初始 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,它会明显更快,而输出流大小的增加可以忽略不计。