我一直在做一些不安全的位图操作,并且发现增加指针的次数可以带来一些重大的性能提升。我不知道为什么会这样,即使你在循环中做了更多的按位操作,对指针进行更少的迭代仍然会更好。
因此,例如,使用UInt64迭代超过32位像素,而不是使用UInt64迭代两个像素,并在一个周期内执行两次操作。
以下是通过读取两个像素并对其进行修改(当然,它会因奇数宽度的图像而失败,但仅用于测试)。
private void removeBlueWithTwoPixelIteration()
{
// think of a big image with data
Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
TimeSpan startTime, endTime;
unsafe {
UInt64 doublePixel;
UInt32 pixel1;
UInt32 pixel2;
const int readSize = sizeof(UInt64);
const UInt64 rightHalf = UInt32.MaxValue;
PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue();
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
byte* image = (byte*)bd.Scan0.ToPointer();
startTime = TimeSpan.FromSeconds(pf.NextValue());
for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride)
{
for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
{
doublePixel = *((UInt64*)pointer);
pixel1 = (UInt32)(doublePixel >> (readSize * 8 / 2)) >> 8; // loose last 8 bits (Blue color)
pixel2 = (UInt32)(doublePixel & rightHalf) >> 8; // loose last 8 bits (Blue color)
*((UInt32*)pointer) = pixel1 << 8; // putback but shift so A R G get back to original positions
*((UInt32*)pointer + 1) = pixel2 << 8; // putback but shift so A R G get back to original positions
}
}
endTime = TimeSpan.FromSeconds(pf.NextValue());
bmp.UnlockBits(bd);
bmp.Dispose();
}
MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString());
}
以下代码逐像素地执行,并且比之前的快70%:
private void removeBlueWithSinglePixelIteration()
{
// think of a big image with data
Bitmap bmp = new Bitmap(15000, 15000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
TimeSpan startTime, endTime;
unsafe
{
UInt32 singlePixel;
const int readSize = sizeof(UInt32);
PerformanceCounter pf = new PerformanceCounter("System", "System Up Time"); pf.NextValue();
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, bmp.PixelFormat);
byte* image = (byte*)bd.Scan0.ToPointer();
startTime = TimeSpan.FromSeconds(pf.NextValue());
for (byte* line = image; line < image + bd.Stride * bd.Height; line += bd.Stride)
{
for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
{
singlePixel = *((UInt32*)pointer) >> 8; // loose B
*((UInt32*)pointer) = singlePixel << 8; // adjust A R G back
}
}
endTime = TimeSpan.FromSeconds(pf.NextValue());
bmp.UnlockBits(bd);
bmp.Dispose();
}
MessageBox.Show((endTime - startTime).TotalMilliseconds.ToString());
}
有人可以澄清为什么增加指针比执行一些按位操作更昂贵的操作?
我正在使用.NET 4框架。
对于C ++来说,这样的事情是真的吗?
NB。 32位与64位相比,两种方法的比例是相等的,但是这两种方式在64位和32位之间的速度相差20%?
编辑:正如Porges和arul所建议的那样,这可能是因为内存读取次数减少和分支开销减少。
EDIT2:
经过一些测试后,似乎从内存中读取更少的时间就是答案:
使用此代码假设图像宽度可被5整除,您可以快400%:
[StructLayout(LayoutKind.Sequential,Pack = 1)]
struct PixelContainer {
public UInt32 pixel1;
public UInt32 pixel2;
public UInt32 pixel3;
public UInt32 pixel4;
public UInt32 pixel5;
}
然后使用:
int readSize = sizeof(PixelContainer);
// .....
for (var pointer = line; pointer < line + bd.Stride; pointer += readSize)
{
multiPixel = *((PixelContainer*)pointer);
multiPixel.pixel1 &= 0xFFFFFF00u;
multiPixel.pixel2 &= 0xFFFFFF00u;
multiPixel.pixel3 &= 0xFFFFFF00u;
multiPixel.pixel4 &= 0xFFFFFF00u;
multiPixel.pixel5 &= 0xFFFFFF00u;
*((PixelContainer*)pointer) = multiPixel;
}
答案 0 :(得分:5)
这是一种称为loop unrolling.的技术。主要的性能优势应该来自减少分支开销。
作为旁注,您可以通过使用位掩码来加快速度:
*((UInt64 *)pointer) &= 0xFFFFFF00FFFFFF00ul;
答案 1 :(得分:2)
指针的递增速度并不慢,而是从内存中读取。使用32位单元,您的读取次数就会增加一倍。
如果在64位版本中编写一次而不是两次,则应该再次找到它。