我有一个方法需要尽可能快,它使用不安全的内存指针,这是我第一次尝试这种类型的编码,所以我知道它可能更快。
/// <summary>
/// Copies bitmapdata from one bitmap to another at a specified point on the output bitmapdata
/// </summary>
/// <param name="sourcebtmpdata">The sourcebitmap must be smaller that the destbitmap</param>
/// <param name="destbtmpdata"></param>
/// <param name="point">The point on the destination bitmap to draw at</param>
private static unsafe void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
{
// calculate total number of rows to draw.
var totalRow = Math.Min(
destbtmpdata.Height - point.Y,
sourcebtmpdata.Height);
//loop through each row on the source bitmap and get mem pointers
//to the source bitmap and dest bitmap
for (int i = 0; i < totalRow; i++)
{
int destRow = point.Y + i;
//get the pointer to the start of the current pixel "row" on the output image
byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride);
//get the pointer to the start of the FIRST pixel row on the source image
byte* srcRowPtr = (byte*)sourcebtmpdata.Scan0 + (i * sourcebtmpdata.Stride);
int pointX = point.X;
//the rowSize is pre-computed before the loop to improve performance
int rowSize = Math.Min(destbtmpdata.Width - pointX, sourcebtmpdata.Width);
//for each row each set each pixel
for (int j = 0; j < rowSize; j++)
{
int firstBlueByte = ((pointX + j)*3);
int srcByte = j *3;
destRowPtr[(firstBlueByte)] = srcRowPtr[srcByte];
destRowPtr[(firstBlueByte) + 1] = srcRowPtr[srcByte + 1];
destRowPtr[(firstBlueByte) + 2] = srcRowPtr[srcByte + 2];
}
}
}
那么有什么办法可以加快速度吗?暂时忽略待办事项,一旦我进行了一些基准性能测量,就会解决问题。
更新:很抱歉,应该提到我使用此而不是Graphics.DrawImage的原因是因为我实现了多线程,因此我无法使用DrawImage。
更新2:我对性能仍然不满意,我确信还有几个ms可以用。
答案 0 :(得分:4)
代码中存在一些根本性的错误,我认为直到现在我还没注意到。
byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride);
这会获得一个指向目标行的指针,但它不会获取它正在复制的列,旧代码中的列是在rowSize循环中完成的。它现在看起来像:
byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride) + pointX * 3;
所以现在我们有了正确的目标数据指针。现在我们可以摆脱for循环。使用Vilx-和Rob中的建议,现在代码如下:
private static unsafe void CopyBitmapToDestSuperFast(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
{
//calculate total number of rows to copy.
//using ternary operator instead of Math.Min, few ms faster
int totalRows = (destbtmpdata.Height - point.Y < sourcebtmpdata.Height) ? destbtmpdata.Height - point.Y : sourcebtmpdata.Height;
//calculate the width of the image to draw, this cuts off the image
//if it goes past the width of the destination image
int rowWidth = (destbtmpdata.Width - point.X < sourcebtmpdata.Width) ? destbtmpdata.Width - point.X : sourcebtmpdata.Width;
//loop through each row on the source bitmap and get mem pointers
//to the source bitmap and dest bitmap
for (int i = 0; i < totalRows; i++)
{
int destRow = point.Y + i;
//get the pointer to the start of the current pixel "row" and column on the output image
byte* destRowPtr = (byte*)destbtmpdata.Scan0 + (destRow * destbtmpdata.Stride) + point.X * 3;
//get the pointer to the start of the FIRST pixel row on the source image
byte* srcRowPtr = (byte*)sourcebtmpdata.Scan0 + (i * sourcebtmpdata.Stride);
//RtlMoveMemory function
CopyMemory(new IntPtr(destRowPtr), new IntPtr(srcRowPtr), (uint)rowWidth * 3);
}
}
将500x500图像复制到网格中的5000x5000图像50次:00:00:07.9948993秒。现在上面的更改需要00:00:01.8714263秒。好多了。
答案 1 :(得分:2)
嗯......我不确定.NET位图数据格式是否完全与Windows的GDI32功能兼容......
但我学到的前几个Win32 API之一是BitBlt:
BOOL BitBlt(
HDC hdcDest,
int nXDest,
int nYDest,
int nWidth,
int nHeight,
HDC hdcSrc,
int nXSrc,
int nYSrc,
DWORD dwRop
);
如果我没记错的话,这是复制数据的最快方式。
这是用于C#和相关使用信息的BitBlt PInvoke签名,对于在C#中使用高性能图形的任何人来说都是一个很好的读物:
绝对值得一看。
答案 2 :(得分:1)
内循环是您想要集中大量时间的地方(但是,要进行测量以确保)
for (int j = 0; j < sourcebtmpdata.Width; j++)
{
destRowPtr[(point.X + j) * 3] = srcRowPtr[j * 3];
destRowPtr[((point.X + j) * 3) + 1] = srcRowPtr[(j * 3) + 1];
destRowPtr[((point.X + j) * 3) + 2] = srcRowPtr[(j * 3) + 2];
}
摆脱乘法和数组索引(这是引擎盖下的乘法)并替换为正在递增的指针。
同上+1,+ 2,增加一个指针。
可能你的编译器不会继续计算point.X(check),但为了以防万一,请创建一个局部变量。它不会在单次迭代中执行,但可能每次迭代都会执行。
答案 3 :(得分:1)
您可能需要查看Eigen。
它是一个C ++模板库,它使用 SSE(2及更高版本)和AltiVec指令集,优雅地回退到非向量化代码。
快速。 (见基准) 表达式模板允许智能地移除临时值并启用延迟评估,在适当的时候 - 在大多数情况下,Eigen会自动处理这种情况并处理混叠。
对SSE(2和更高版本)和AltiVec指令集执行显式矢量化,优雅地回退到非矢量化代码。表达式模板允许对整个表达式全局执行这些优化 对于固定大小的对象,可以避免动态内存分配,并在有意义时展开循环 对于大型矩阵,特别注意缓存友好性。
您可以在C ++中实现函数,然后从C#
调用它答案 4 :(得分:1)
您并不总是需要使用指针来获得良好的速度。这应该在原始的几毫秒内:
private static void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
{
byte[] src = new byte[sourcebtmpdata.Height * sourcebtmpdata.Width * 3];
int maximum = src.Length;
byte[] dest = new byte[maximum];
Marshal.Copy(sourcebtmpdata.Scan0, src, 0, src.Length);
int pointX = point.X * 3;
int copyLength = destbtmpdata.Width*3 - pointX;
int k = pointX + point.Y * sourcebtmpdata.Stride;
int rowWidth = sourcebtmpdata.Stride;
while (k<maximum)
{
Array.Copy(src,k,dest,k,copyLength);
k += rowWidth;
}
Marshal.Copy(dest, 0, destbtmpdata.Scan0, dest.Length);
}
答案 5 :(得分:1)
不幸的是我没有时间编写完整的解决方案,但我会考虑使用平台 RtlMoveMemory()函数来整行移动行,而不是逐字节移动。这应该快得多。
答案 6 :(得分:0)
我认为可以提前计算步幅大小和行数限制。
我预先计算了所有乘法,得到以下代码:
private static unsafe void CopyBitmapToDest(BitmapData sourcebtmpdata, BitmapData destbtmpdata, Point point)
{
//TODO: It is expected that the bitmap PixelFormat is Format24bppRgb but this could change in the future
const int pixelSize = 3;
// calculate total number of rows to draw.
var totalRow = Math.Min(
destbtmpdata.Height - point.Y,
sourcebtmpdata.Height);
var rowSize = Math.Min(
(destbtmpdata.Width - point.X) * pixelSize,
sourcebtmpdata.Width * pixelSize);
// starting point of copy operation
byte* srcPtr = (byte*)sourcebtmpdata.Scan0;
byte* destPtr = (byte*)destbtmpdata.Scan0 + point.Y * destbtmpdata.Stride;
// loop through each row
for (int i = 0; i < totalRow; i++) {
// draw the entire row
for (int j = 0; j < rowSize; j++)
destPtr[point.X + j] = srcPtr[j];
// advance each pointer by 1 row
destPtr += destbtmpdata.Stride;
srcPtr += sourcebtmpdata.Stride;
}
}
没有彻底测试过,但你应该能够让它发挥作用。
我已经从循环中删除了乘法运算(改为预先计算)并删除了大多数分支,因此它应该更快一些。
请告诉我这是否有帮助: - )
答案 7 :(得分:0)
我正在查看您的C#代码,我无法识别任何熟悉的内容。这一切看起来都像是一大堆C ++。顺便说一句,看起来DirectX / XNA需要成为你的新朋友。只需2美分。不要杀死信使。
如果你必须依靠CPU来做到这一点:我自己做了一些24位布局优化,我可以告诉你,内存访问速度应该是你的瓶颈。使用SSE3指令进行最快的逐字节访问。这意味着C ++和嵌入式汇编语言。在纯C中,你在大多数机器上的速度会慢30%。
请记住,在这种操作中,现代GPU比CPU快得多。
答案 8 :(得分:0)
我不确定这是否会带来额外的性能,但我在Reflector中看到了很多模式。
所以:
int srcByte = j *3;
destRowPtr[(firstBlueByte)] = srcRowPtr[srcByte];
destRowPtr[(firstBlueByte) + 1] = srcRowPtr[srcByte + 1];
destRowPtr[(firstBlueByte) + 2] = srcRowPtr[srcByte + 2];
变为:
*destRowPtr++ = *srcRowPtr++;
*destRowPtr++ = *srcRowPtr++;
*destRowPtr++ = *srcRowPtr++;
可能需要更多支撑。
如果宽度固定,您可以将整行展开为几百行。 :)
<强>更新强>
您也可以尝试使用更大的类型,例如Int32或Int64以获得更好的性能。
答案 9 :(得分:0)
好吧,这将非常接近你可以从算法中获得多少ms的行,但是除去对Math.Min的调用并将其替换为三元运算符。
通常,进行库调用需要的时间比自己做的事情要长,我制作了一个简单的测试驱动程序来确认Math.Min.
using System;
using System.Diagnostics;
namespace TestDriver
{
class Program
{
static void Main(string[] args)
{
// Start the stopwatch
if (Stopwatch.IsHighResolution)
{ Console.WriteLine("Using high resolution timer"); }
else
{ Console.WriteLine("High resolution timer unavailable"); }
// Test Math.Min for 10000 iterations
Stopwatch sw = Stopwatch.StartNew();
for (int ndx = 0; ndx < 10000; ndx++)
{
int result = Math.Min(ndx, 5000);
}
Console.WriteLine(sw.Elapsed.TotalMilliseconds.ToString("0.0000"));
// Test trinary operator for 10000 iterations
sw = Stopwatch.StartNew();
for (int ndx = 0; ndx < 10000; ndx++)
{
int result = (ndx < 5000) ? ndx : 5000;
}
Console.WriteLine(sw.Elapsed.TotalMilliseconds.ToString("0.0000"));
Console.ReadKey();
}
}
}
在我的电脑上运行上述内容时的结果,Intel T2400 @ 1.83GHz。此外,请注意结果有一些变化,但通常trinay运算符更快约0.01 ms。这并不多,但是在一个足够大的数据集上它会加起来。
使用高分辨率计时器
0.0539
0.0402