使用 x86 汇编实现 C# 的快速内存拷贝

开发 后端
本文将为大家展示和介绍怎么样在C#和.NET下使用汇编秒速拷贝数据,在我是实例里面我用了一运用程序创建了一段视频,里面包含图片,视频和声音。

介绍
 

大家好,我是Oleksandr Karpov,这个是我***次发表文章,希望大家喜欢。

在这我将为大家展示和介绍怎么样在C#和.NET下使用汇编秒速拷贝数据,在我是实例里面我用了一运用程序创建了一段视频,里面包含图片,视频和声音。

当然如果你也需要在C#使用汇编的情况,这方法给你提供一个快速简单的解决途径。

背景

理解本文的内容, ***具备以下知识: 汇编语言, 内存对齐, c#, windows 和 .net 高级技巧(advanced techniques).
 要提高数据复制(copy-past )的速度, 我们需要将内存地址按 16 个字节对齐. 否则, 速度不会有明显的改变. (我的例子大概快 1.02 倍 )
 
Pentium III+ (KNI/MMX2) 和 AMD Athlon (AMD EMMX) 这两种处理器都支持本文代码用到 SSE 指令集.

我用配置为: Pentium Dual-Core E5800 3.2GHz, 4GB 双通道内存的计算机做测试, 16 个字节内存对齐的速度要比标准方式快 1.5 倍, 而非内存对齐方式的速度几乎没有变化(1.02倍).

使用代码

这是一个完整的演示测试,向你展示了性能测试以及如何使用。

FastMemCopy   类包含了用于快速内存拷贝逻辑的所有内容。

首先你需要创建一个默认的Windows Forms应用程序工程,在窗体上放两个按钮,一个PictureBox 控件,因为我们将用图片来测试。

声明几个字段先:

  1. string bitmapPath;  
  2. Bitmap bmp, bmp2;  
  3. BitmapData bmpd, bmpd2;  
  4. byte[] buffer = null

现在创建两个方法用来处理按钮的点击事件。

标准方法如下:

  1. private void btnStandard_Click(object sender, EventArgs e)  
  2. {  
  3.         using (OpenFileDialog ofd = new OpenFileDialog())  
  4.         {  
  5.             if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK)  
  6.                 return;  
  7.    
  8.             bitmapPath = ofd.FileName;  
  9.         }  
  10.    
  11.   //open a selected image and create an empty image with the same size  
  12.         OpenImage();  
  13.    
  14.   //unlock for read and write images  
  15.         UnlockBitmap();  
  16.    
  17.   //copy data from one image to another by standard method  
  18.         CopyImage();  
  19.    
  20.   //lock images to be able to see them  
  21.         LockBitmap();  
  22.    
  23.   //lets see what we have  
  24.         pictureBox1.Image = bmp2;  

快速方法如下:

  1. private void btnFast_Click(object sender, EventArgs e)  
  2. {  
  3.   using (OpenFileDialog ofd = new OpenFileDialog())  
  4.         {  
  5.             if (ofd.ShowDialog() != System.Windows.Forms.DialogResult.OK)  
  6.                 return;  
  7.             bitmapPath = ofd.FileName;  
  8.         }  
  9.    
  10.   //open a selected image and create an empty image with the same size  
  11.         OpenImage();  
  12.    
  13.   //unlock for read and write images  
  14.         UnlockBitmap();  
  15.    
  16.   //copy data from one image to another with our fast method  
  17.         FastCopyImage();  
  18.    
  19.   //lock images to be able to see them  
  20.         LockBitmap();  
  21.    
  22.   //lets see what we have  
  23.         pictureBox1.Image = bmp2;  

好的,现在我们有按钮并且也有了事件处理,下面来实现打开图片、锁定、解锁它们的方法,以及标准拷贝方法:

打开一个图片:

  1. void OpenImage()  
  2. {  
  3.   pictureBox1.Image = null;  
  4.   buffer = null;  
  5.   if (bmp != null)  
  6.   {  
  7.     bmp.Dispose();  
  8.     bmp = null;  
  9.   }  
  10.   if (bmp2 != null)  
  11.   {  
  12.     bmp2.Dispose();  
  13.     bmp2 = null;  
  14.   }  
  15.   GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);  
  16.    
  17.   bmp = (Bitmap)Bitmap.FromFile(bitmapPath);  
  18.    
  19.   buffer = new byte[bmp.Width * 4 * bmp.Height];  
  20.   bmp2 = new Bitmap(bmp.Width, bmp.Height, bmp.Width * 4, PixelFormat.Format32bppArgb,  
  21.     Marshal.UnsafeAddrOfPinnedArrayElement(buffer, 0));  

锁定和解锁位图:

  1. void UnlockBitmap()  
  2. {  
  3.   bmpd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite,   
  4.     PixelFormat.Format32bppArgb);  
  5.   bmpd2 = bmp2.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite,   
  6.     PixelFormat.Format32bppArgb);  
  7. }  
  8.    
  9. void LockBitmap()  
  10. {  
  11.   bmp.UnlockBits(bmpd);  
  12.   bmp2.UnlockBits(bmpd2);  

从一个图片拷贝数据到另一个图片,并且显示测得的时间:

  1. void CopyImage()  
  2. {  
  3.   //start stopwatch  
  4.   Stopwatch sw = new Stopwatch();  
  5.   sw.Start();  
  6.    
  7.   //copy-past data 10 times  
  8.   for (int i = 0; i < 10; i++)  
  9.   {  
  10.     System.Runtime.InteropServices.Marshal.Copy(bmpd.Scan0, buffer, 0, buffer.Length);  
  11.   }  
  12.    
  13.   //stop stopwatch  
  14.   sw.Stop();  
  15.    
  16.   //show measured time  
  17.   MessageBox.Show(sw.ElapsedTicks.ToString());  

这就是标准快速拷贝方法。其实一点也不复杂,我们使用了知名的  System.Runtime.InteropServices.Marshal.Copy   方法。

以及又一个“中间方法(middle-method)”以用于快速拷贝逻辑:

  1. void FastCopyImage()  
  2. {  
  3.   FastMemCopy.FastMemoryCopy(bmpd.Scan0, bmpd2.Scan0, buffer.Length);  

现在,来实现FastMemCopy类。下面是类的声明以及我们将会在类中使用到的一些类型:

  1. internal static class FastMemCopy  
  2. {  
  3.   [Flags]  
  4.   private enum AllocationTypes : uint 
  5.   {  
  6.     Commit = 0x1000,  Reserve = 0x2000,  
  7.     Reset = 0x80000,  LargePages = 0x20000000,  
  8.     Physical = 0x400000,  TopDown = 0x100000,  
  9.     WriteWatch = 0x200000  
  10.   }  
  11.    
  12.   [Flags]  
  13.   private enum MemoryProtections : uint 
  14.   {  
  15.     Execute = 0x10,      ExecuteRead = 0x20,  
  16.     ExecuteReadWrite = 0x40,  ExecuteWriteCopy = 0x80,  
  17.     NoAccess = 0x01,    ReadOnly = 0x02,  
  18.     ReadWrite = 0x04,    WriteCopy = 0x08,  
  19.     GuartModifierflag = 0x100,  NoCacheModifierflag = 0x200,  
  20.     WriteCombineModifierflag = 0x400  
  21.   }  
  22.    
  23.   [Flags]  
  24.   private enum FreeTypes : uint 
  25.   {  
  26.     Decommit = 0x4000,  Release = 0x8000  
  27.   }  
  28.    
  29.   [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]  
  30.   private unsafe delegate void FastMemCopyDelegate();  
  31.    
  32.   private static class NativeMethods  
  33.   {  
  34.     [DllImport("kernel32.dll", SetLastError = true)]  
  35.     internal static extern IntPtr VirtualAlloc(  
  36.       IntPtr lpAddress,  
  37.       UIntPtr dwSize,  
  38.       AllocationTypes flAllocationType,  
  39.       MemoryProtections flProtect);  
  40.    
  41.     [DllImport("kernel32")]  
  42.     [return: MarshalAs(UnmanagedType.Bool)]  
  43.     internal static extern bool VirtualFree(  
  44.       IntPtr lpAddress,  
  45.       uint dwSize,  
  46.       FreeTypes flFreeType);  
  47.   } 

现在声明方法本身:

  1. public static unsafe void FastMemoryCopy(IntPtr src, IntPtr dst, int nBytes)  
  2. {  
  3.   if (IntPtr.Size == 4)  
  4.         {  
  5.                 //we are in 32 bit mode  
  6.    
  7.                 //allocate memory for our asm method  
  8.                 IntPtr p = NativeMethods.VirtualAlloc(  
  9.                     IntPtr.Zero,  
  10.                     new UIntPtr((uint)x86_FastMemCopy_New.Length),  
  11.                     AllocationTypes.Commit | AllocationTypes.Reserve,  
  12.                     MemoryProtections.ExecuteReadWrite);  
  13.    
  14.                 try 
  15.                 {  
  16.                     //copy our method bytes to allocated memory  
  17.                     Marshal.Copy(x86_FastMemCopy_New, 0, p, x86_FastMemCopy_New.Length);  
  18.    
  19.                     //make a delegate to our method  
  20.                     FastMemCopyDelegate _fastmemcopy =   
  21.       (FastMemCopyDelegate)Marshal.GetDelegateForFunctionPointer(p,   
  22.         typeof(FastMemCopyDelegate));  
  23.    
  24.                     //offset to the end of our method block  
  25.                     p += x86_FastMemCopy_New.Length;  
  26.    
  27.                     //store length param  
  28.                     p -= 8;  
  29.                     Marshal.Copy(BitConverter.GetBytes((long)nBytes), 0, p, 4);  
  30.    
  31.                     //store destination address param  
  32.                     p -= 8;  
  33.                     Marshal.Copy(BitConverter.GetBytes((long)dst), 0, p, 4);  
  34.    
  35.                     //store source address param  
  36.                     p -= 8;  
  37.                     Marshal.Copy(BitConverter.GetBytes((long)src), 0, p, 4);  
  38.    
  39.                     //Start stopwatch  
  40.                     Stopwatch sw = new Stopwatch();  
  41.                     sw.Start();  
  42.    
  43.                     //copy-past all data 10 times  
  44.                     for (int i = 0; i < 10; i++)  
  45.                         _fastmemcopy();  
  46.    
  47.                     //stop stopwatch  
  48.                     sw.Stop();  
  49.    
  50.                     //get message with measured time  
  51.                     System.Windows.Forms.MessageBox.Show(sw.ElapsedTicks.ToString());  
  52.                 }  
  53.                 catch (Exception ex)  
  54.                 {  
  55.                     //if any exception  
  56.                     System.Windows.Forms.MessageBox.Show(ex.Message);  
  57.                 }  
  58.                 finally 
  59.                 {  
  60.                     //free allocated memory  
  61.                     NativeMethods.VirtualFree(p, (uint)(x86_FastMemCopy_New.Length),   
  62.       FreeTypes.Release);  
  63.                     GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);  
  64.                 }  
  65.   }  
  66.   else if (IntPtr.Size == 8)  
  67.         {  
  68.                 throw new ApplicationException("x64 is not supported yet!");  
  69.   }  

汇编代码被表示成带注释的字节数组:

  1. private static byte[] x86_FastMemCopy_New = new byte[]  
  2. {  
  3.   0x90, //nop do nothing  
  4.   0x60, //pushad store flag register on stack  
  5.   0x95, //xchg ebp, eax eax contains memory address of our method  
  6.   0x8B, 0xB5, 0x***, 0x01, 0x00, 0x00, //mov esi,[ebp][0000001***] get source buffer address  
  7.   0x89, 0xF0, //mov eax,esi  
  8.   0x83, 0xE0, 0x0F, //and eax,00F will check if it is 16 byte aligned  
  9.   0x8B, 0xBD, 0x62, 0x01, 0x00, 0x00, //mov edi,[ebp][000000162] get destination address  
  10.   0x89, 0xFB, //mov ebx,edi  
  11.   0x83, 0xE3, 0x0F, //and ebx,00F will check if it is 16 byte aligned  
  12.   0x8B, 0x8D, 0x6A, 0x01, 0x00, 0x00, //mov ecx,[ebp][00000016A] get number of bytes to copy  
  13.   0xC1, 0xE9, 0x07, //shr ecx,7 divide length by 128  
  14.   0x85, 0xC9, //test ecx,ecx check if zero  
  15.   0x0F, 0x84, 0x1C, 0x01, 0x00, 0x00, //jz 000000146 &darr; copy the rest  
  16.   0x0F, 0x18, 0x06, //prefetchnta [esi] pre-fetch non-temporal source data for reading  
  17.   0x85, 0xC0, //test eax,eax check if source address is 16 byte aligned  
  18.   0x0F, 0x84, 0x8B, 0x00, 0x00, 0x00, //jz 0000000C0 &darr; go to copy if aligned  
  19.   0x0F, 0x18, 0x86, 0x80, 0x02, 0x00, 0x00, //prefetchnta [esi][000000280] pre-fetch more source data  
  20.   0x0F, 0x10, 0x06, //movups xmm0,[esi] copy 16 bytes of source data  
  21.   0x0F, 0x10, 0x4E, 0x10, //movups xmm1,[esi][010] copy more 16 bytes  
  22.   0x0F, 0x10, 0x56, 0x20, //movups xmm2,[esi][020] copy more  
  23.   0x0F, 0x18, 0x86, 0xC0, 0x02, 0x00, 0x00, //prefetchnta [esi][0000002C0] pre-fetch more  
  24.   0x0F, 0x10, 0x5E, 0x30, //movups xmm3,[esi][030]  
  25.   0x0F, 0x10, 0x66, 0x40, //movups xmm4,[esi][040]  
  26.   0x0F, 0x10, 0x6E, 0x50, //movups xmm5,[esi][050]  
  27.   0x0F, 0x10, 0x76, 0x60, //movups xmm6,[esi][060]  
  28.   0x0F, 0x10, 0x7E, 0x70, //movups xmm7,[esi][070] we&apos;ve copied 128 bytes of source data  
  29.   0x85, 0xDB, //test ebx,ebx check if destination address is 16 byte aligned  
  30.   0x74, 0x21, //jz 000000087 &darr; go to past if aligned  
  31.   0x0F, 0x11, 0x07, //movups [edi],xmm0 past first 16 bytes to non-aligned destination address  
  32.   0x0F, 0x11, 0x4F, 0x10, //movups [edi][010],xmm1 past more  
  33.   0x0F, 0x11, 0x57, 0x20, //movups [edi][020],xmm2  
  34.   0x0F, 0x11, 0x5F, 0x30, //movups [edi][030],xmm3  
  35.   0x0F, 0x11, 0x67, 0x40, //movups [edi][040],xmm4  
  36.   0x0F, 0x11, 0x6F, 0x50, //movups [edi][050],xmm5  
  37.   0x0F, 0x11, 0x77, 0x60, //movups [edi][060],xmm6  
  38.   0x0F, 0x11, 0x7F, 0x70, //movups [edi][070],xmm7 we&apos;ve pasted 128 bytes of source data  
  39.   0xEB, 0x1F, //jmps 0000000A6 &darr; continue  
  40.   0x0F, 0x2B, 0x07, //movntps [edi],xmm0 past first 16 bytes to aligned destination address  
  41.   0x0F, 0x2B, 0x4F, 0x10, //movntps [edi][010],xmm1 past more  
  42.   0x0F, 0x2B, 0x57, 0x20, //movntps [edi][020],xmm2  
  43.   0x0F, 0x2B, 0x5F, 0x30, //movntps [edi][030],xmm3  
  44.   0x0F, 0x2B, 0x67, 0x40, //movntps [edi][040],xmm4  
  45.   0x0F, 0x2B, 0x6F, 0x50, //movntps [edi][050],xmm5  
  46.   0x0F, 0x2B, 0x77, 0x60, //movntps [edi][060],xmm6  
  47.   0x0F, 0x2B, 0x7F, 0x70, //movntps [edi][070],xmm7 we&apos;ve pasted 128 bytes of source data  
  48.   0x81, 0xC6, 0x80, 0x00, 0x00, 0x00, //add esi,000000080 increment source address by 128  
  49.   0x81, 0xC7, 0x80, 0x00, 0x00, 0x00, //add edi,000000080 increment destination address by 128  
  50.   0x83, 0xE9, 0x01, //sub ecx,1 decrement counter  
  51.   0x0F, 0x85, 0x7A, 0xFF, 0xFF, 0xFF, //jnz 000000035 &uarr; continue if not zero  
  52.   0xE9, 0x86, 0x00, 0x00, 0x00, //jmp 000000146 &darr; go to copy the rest of data  
  53.    
  54.   0x0F, 0x18, 0x86, 0x80, 0x02, 0x00, 0x00, //prefetchnta [esi][000000280] pre-fetch source data  
  55.   0x0F, 0x28, 0x06, //movaps xmm0,[esi] copy 128 bytes from aligned source address  
  56.   0x0F, 0x28, 0x4E, 0x10, //movaps xmm1,[esi][010] copy more  
  57.   0x0F, 0x28, 0x56, 0x20, //movaps xmm2,[esi][020]  
  58.   0x0F, 0x18, 0x86, 0xC0, 0x02, 0x00, 0x00, //prefetchnta [esi][0000002C0] pre-fetch more data  
  59.   0x0F, 0x28, 0x5E, 0x30, //movaps xmm3,[esi][030]  
  60.   0x0F, 0x28, 0x66, 0x40, //movaps xmm4,[esi][040]  
  61.   0x0F, 0x28, 0x6E, 0x50, //movaps xmm5,[esi][050]  
  62.   0x0F, 0x28, 0x76, 0x60, //movaps xmm6,[esi][060]  
  63.   0x0F, 0x28, 0x7E, 0x70, //movaps xmm7,[esi][070] we&apos;ve copied 128 bytes of source data  
  64.   0x85, 0xDB, //test ebx,ebx check if destination address is 16 byte aligned  
  65.   0x74, 0x21, //jz 000000112 &darr; go to past if aligned  
  66.   0x0F, 0x11, 0x07, //movups [edi],xmm0 past 16 bytes to non-aligned destination address  
  67.   0x0F, 0x11, 0x4F, 0x10, //movups [edi][010],xmm1 past more  
  68.   0x0F, 0x11, 0x57, 0x20, //movups [edi][020],xmm2  
  69.   0x0F, 0x11, 0x5F, 0x30, //movups [edi][030],xmm3  
  70.   0x0F, 0x11, 0x67, 0x40, //movups [edi][040],xmm4  
  71.   0x0F, 0x11, 0x6F, 0x50, //movups [edi][050],xmm5  
  72.   0x0F, 0x11, 0x77, 0x60, //movups [edi][060],xmm6  
  73.   0x0F, 0x11, 0x7F, 0x70, //movups [edi][070],xmm7 we&apos;ve pasted 128 bytes of data  
  74.   0xEB, 0x1F, //jmps 000000131 &darr; continue copy-past  
  75.   0x0F, 0x2B, 0x07, //movntps [edi],xmm0 past 16 bytes to aligned destination address  
  76.   0x0F, 0x2B, 0x4F, 0x10, //movntps [edi][010],xmm1 past more  
  77.   0x0F, 0x2B, 0x57, 0x20, //movntps [edi][020],xmm2  
  78.   0x0F, 0x2B, 0x5F, 0x30, //movntps [edi][030],xmm3  
  79.   0x0F, 0x2B, 0x67, 0x40, //movntps [edi][040],xmm4  
  80.   0x0F, 0x2B, 0x6F, 0x50, //movntps [edi][050],xmm5  
  81.   0x0F, 0x2B, 0x77, 0x60, //movntps [edi][060],xmm6  
  82.   0x0F, 0x2B, 0x7F, 0x70, //movntps [edi][070],xmm7 we&apos;ve pasted 128 bytes of data  
  83.   0x81, 0xC6, 0x80, 0x00, 0x00, 0x00, //add esi,000000080 increment source address by 128  
  84.   0x81, 0xC7, 0x80, 0x00, 0x00, 0x00, //add edi,000000080 increment destination address by 128  
  85.   0x83, 0xE9, 0x01, //sub ecx,1 decrement counter  
  86.   0x0F, 0x85, 0x7A, 0xFF, 0xFF, 0xFF, //jnz 0000000C0 &uarr; continue copy-past if non-zero  
  87.   0x8B, 0x8D, 0x6A, 0x01, 0x00, 0x00, //mov ecx,[ebp][00000016A] get number of bytes to copy  
  88.   0x83, 0xE1, 0x7F, //and ecx,07F get rest number of bytes  
  89.   0x85, 0xC9, //test ecx,ecx check if there are bytes  
  90.   0x74, 0x02, //jz 000000155 &darr; exit if there are no more bytes  
  91.   0xF3, 0xA4, //rep movsb copy rest of bytes  
  92.   0x0F, 0xAE, 0xF8, //sfence performs a serializing operation on all store-to-memory instructions  
  93.   0x61, //popad restore flag register  
  94.   0xC3, //retn return from our method to C#  
  95.      
  96.   0x00, 0x00, 0x00, 0x00, //source buffer address  
  97.   0x00, 0x00, 0x00, 0x00,  
  98.    
  99.   0x00, 0x00, 0x00, 0x00, //destination buffer address  
  100.   0x00, 0x00, 0x00, 0x00,  
  101.    
  102.   0x00, 0x00, 0x00, 0x00, //number of bytes to copy-past  
  103.   0x00, 0x00, 0x00, 0x00  
  104. }; 

我们将会通过前面创建的托管来调用汇编方法。

该方法目前工作在32位模式下,将来我会实现64位模式。

谁感兴趣的话可以添加到源代码中(文章中几乎包含了所有的代码)

兴趣点

在实现及测试该方法期间,我发现prefetchnta命令描述的不是很清楚,甚至是Intel的说明书也是一样。所以我尝试自己以及通过google来弄明白[[125062]]。注意movntps和movaps说明,它们只在16字节内存地址对齐时工作。

英文原文:C# - Fast memory copy method with x86 assembly usage

译文出自:http://www.oschina.net/translate/csharp-fast-memory-copy-method-with-x-assembly-usa

责任编辑:林师授 来源: 开源中国社区编译
相关推荐

2021-06-07 15:20:22

Linux X861MB内存BIOS

2011-12-01 11:09:48

AMDx86服务器英特尔

2021-03-25 13:05:56

网络安全寄存器汇编语言

2021-07-07 11:35:17

Linux内存段寻址

2013-01-31 10:04:20

x86服务器虚拟化

2011-11-10 09:26:48

Solaris 11

2009-08-28 14:38:33

2011-12-19 10:55:58

云计算中国电信

2011-02-20 22:23:43

X86虚拟化XenServer

2010-05-07 17:47:12

Unix Solari

2019-03-22 08:25:20

x86PythonARM

2010-02-04 16:27:24

Android X86

2010-08-27 10:04:33

X86ARM架构Facebook

2021-08-16 13:26:49

Linuxx86 Linux

2009-08-05 09:02:26

Microsoft FIIS 7.0

2010-03-20 11:03:13

VMControl虚拟化管理

2010-04-29 17:50:15

2013-08-07 09:55:05

IBMGoogleNVIDIA

2014-03-14 09:58:49

服务器ARMx86

2011-05-23 17:00:29

点赞
收藏

51CTO技术栈公众号