不安全的指针迭代和位图 – 为什么UInt64更快?

我一直在做一些不安全的位图操作,并发现增加指针的次数可以带来一些重大的性能提升。 我不知道为什么会这样,即使你在循环中做了更多的按位操作,对指针进行更少的迭代仍然会更好。

因此,例如,使用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 > (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 ARG get back to original positions *((UInt32*)pointer + 1) = pixel2 << 8; // putback but shift so ARG 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 > 8; // loose B *((UInt32*)pointer) = singlePixel << 8; // adjust ARG 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; } 

这是一种称为循环展开的技术。 主要的性能优势应该来自减少分支开销。

作为旁注,您可以通过使用位掩码来加快速度:

 *((UInt64 *)pointer) &= 0xFFFFFF00FFFFFF00ul; 

这不是增加指针的速度,而是从内存中读取。 使用32位单元,您的读取数量是其两倍。

如果在64位版本中编写一次而不是两次,则应该再次找到它。