Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1798061
  • 博文数量: 438
  • 博客积分: 9799
  • 博客等级: 中将
  • 技术积分: 6092
  • 用 户 组: 普通用户
  • 注册时间: 2012-03-25 17:25
文章分类

全部博文(438)

文章存档

2019年(1)

2013年(8)

2012年(429)

分类: 嵌入式

2012-03-27 13:33:24

Charles Petzold

通常,我们把代码和数据区别对待。但对于机器而言,代码和数据本质上其实是一样的——不过是一堆字节码而已。

在Windows1.0里面,有一个图形函数叫做BitBlt,意即“Bit block transfer(位块传输)”。它将一个矩形像素块传输到另一个地方,比如把一个位图拷贝到显示器上。在这个拷贝过程中,我们希望加上一些其它功能,比 如在传输过程中对每个像素求逆(即黑变白,浅灰变深灰,绿变绛红等)。BitBlt(和StretchBlt)实现的光栅操作涉及了三个对象:源,目标和 模式(画刷),因此也被称为三元光栅操作。一共有256种光栅操作,下面是用C语言实现的一个版本,其中S为源,D为目标,P为画刷:


  1. for (y = 0; y < cy; y++)
  2. for (x = 0; x < cx; x++)
  3. {
  4.     switch (rop)
  5.     {
  6.     case 0x00:
  7.         D[y][x] = 0x00;
  8.         break;
  9.     ...
  10.     case 0x60:
  11.         D[y][x] = (D[y][x] ^ S[y][x]) & P[y][x];
  12.         break;
  13.     ...
  14.     case 0xFF:
  15.         D[y][x] = 0xFF;
  16.         break;
  17.     }
  18. }

上面的代码省略了大部份的操作,但它确实包含256个case。因为它操作的是位图,如今一台廉价数码相机的位图都能包含百万级像素,所以将大大的switch语句放入在这个双重循环体里,性能将会很差,为此,可以把双重循环移到每个case子句中:


  1. switch (rop)
  2. {
  3. case 0x00:
  4.     for (y = 0; y < cy; y++)
  5.     for (x = 0; x < cx; x++)
  6.         D[y][x] = 0x00;
  7.         break;
  8.     ...
  9. case 0x60:
  10.     for (y = 0; y < cy; y++)
  11.     for (x = 0; x < cx; x++)
  12.         D[y][x] = (D[y][x] ^ S[y][x]) & P[y][x];
  13.         break;
  14.     ...
  15. case 0xFF:
  16.     for (y = 0; y < cy; y++)
  17.     for (x = 0; x < cx; x++)
  18.         D[y][x] = 0xFF;
  19.         break;
  20.     }
  21. }

这样虽然代码看上去有些丑陋,但性能却得到很大的提高。(博主:书上并没有说明为什么这样做可以提升性能,但我分析原因如下:1、省去了百万次的跳转(到 switch表中的某项)的操作;2、每次for循环同样对应着一次跳转操作,如果for包含着switch语句,那么跳转操作要跨过整个switch 表,但如果把for循环局限在case子句中的话,跳转操作只需跨过case子句,这样可以减少内存页面失效的可能性。)

BitBlt操作是Windows里极其重要的函数,所以是尽可能地让它快,为此,微软的天才程序员想出了一种“比汇编还快”的手法:BitBlt包含一 个“迷你”编译器,基于参数中给定的特定的光栅操作,在栈上建立起一个由8086机器代码构成的子进程,然后执行它。这个即时建立起来的由机器代码构成的 子进程不需要switch,而只需要对每个像素执行给定的光栅操作即可。

 

过滤器是一个矩阵,它用如下方式来处理源位图中的所有像素:把过滤器的中心与当前处理的像素重合,再将源位图中与过滤器重叠的所有的像素根据过滤器中的元素值,计算出目标位图中对应的像素的值。它的C语言实现如下:


  1. for (yDestination = 0; yDestination < cyBitmap; yDestination++)
  2. for (xDestination = 0; xDestination < cxBitmap; xDestination++)
  3. {
  4.     double pixelsAccum = 0;
  5.     double filterAccum = 0;

  6.     for (yFilter = 0; yFilter < cyFilter; yFilter++)
  7.     for (xFilter = 0; xFilter < cxFilter; xFilter++)
  8.     {
  9.         int ySource = yDestination + yFilter - cyFilter/2;
  10.         int xSource = xDestination + xFilter - cxFilter/2;

  11.         if (ySource >= 0 && ySource < cyBitmap && xSource >= 0 && xSource < cxBitmap)
  12.         {
  13.             pixelsAccum += F[yFilter][xFilter] * S[ySource][xSource];
  14.             filterAccum += F[yFilter][xFilter];
  15.         }
  16.     }
  17.     if (filterAccum != 0)
  18.         pixelAccum /= filterAccum;

  19.     if (pixelsAccum < 0)
  20.         D[yDestination][xDestination] = 0;
  21.     else if (pixelsAccum > 255)
  22.         D[yDestination][xDestination] = 255;
  23.     else
  24.         D[yDestination][xDestination] = (unsigned char) pixelsAccum;
  25. }


如今我们不太使用汇编或C了,而是用更高级的语言,比如C#,所以我们尝试用C#来实现上面的过滤器。但是,C#的二维数组要用到方法调用,造成性能的损失。而C#的一维数组可以得到指令级的支持,所以我们使用一维数组来表示位图:



  1.       void FilterMethodCS(byte[] src, byte[] dst, int stride, int bytesPerPixel)
  2.       {
  3.           int cBytes = src.Length;
  4.           int cFilter = filter.Length;
  5.           
  6.           for (int iDst = 0; iDst < cBytes; iDst++)
  7.           {
  8.               double pixelsAccum = 0;
  9.               double filterAccum - 0;
  10.              for (int iFilter = 0; iFilter < cFilter; iFilter++)
  11.              {
  12.                  int yFilter = iFilter / cyFilter;
  13.                  int xFilter = iFilter % cxFilter;
  14.                  int iSrc = iDst + stride * (yFilter - cyFilter / 2) +
  15.                         bytesPerPixel * (xFilter - cxFilter / 2);
  16.                  if (iSrc >= 0 && iSrc < cBytes)
  17.                  {
  18.                      pixelsAccum += filter[iFilter] * src[iSrc];
  19.                      filterAccum += filter[iFilter];
  20.                  }
  21.              }
  22.              if (filterAccum != 0)
  23.                  pixelsAccum /= filterAccum;
  24.              dst[iDst] = pixelsAccum < 0 ? (byte)0 : (pixelsAccum > 255 ?
  25.                          (byte)255 : (byte)pixelsAccum);
  26.          }
  27.      }

上面的代码执行起来比较慢,怎么让它快一些呢?怎么样对它进行优化呢?你可能会想直接用C#的中间语言编写,但真的可以写的比C#编译器生成的中间语言代 码还好吗?FilterMethodCS的真正问题在于它是一个泛化了的方法:它能够对任意大小的位图利用任意大小的过滤阵列。它的大量代码都是在循环和 索引。如果它不需要应对这样一般性的情况的话,其效率便可以大大提高了。比如,一个3乘3的过滤阵列:

F11 F12 F13
F21 F22 F23
F31 F32 F33

如果我们不用循环,而是直接硬编码:


  1. // 过滤器元素F11
  2. int iSrc = iDst - 4 * CX - 4;

  3. if (iSrc >= 0 && iSrc < 4 * CX * CY)
  4. {
  5.     pixelsAccum += src[iSrc] * F11;
  6.     filterAccum += F11;
  7. }

  8. // 过滤器元素F12
  9. int iSrc = iDst - 4 * CX - 4;

  10. if (iSrc >= 0 && iSrc < 4 * CX * CY)
  11. {
  12.     pixelsAccum += src[iSrc] * F12;
  13.     filterAccum += F12;
  14. }

  15. // 过滤器元素F13到F32的过滤元素
  16. ...

  17. // 过滤器元素F33
  18. int iSrc = iDst - 4 * CX - 4;

  19. if (iSrc >= 0 && iSrc < 4 * CX * CY)
  20. {
  21.     pixelsAccum += src[iSrc] * F33;
  22.     filterAccum += F33;
  23. }

由于我们事先并不知道过滤阵列的大小,所以我们不能用上面的“笨”方法。但换个角度想,我们可以学习BitBlt的经验,动态生成代码,再执行这些代码, 为此我们要使用到System.Reflection.Emit中的一组类。下面是FilterMethodCS的另一个版本的实现:


  1. void FilterMethodIL(byte[] src, byte[] dst, int stride, int bytesPerPixel)
  2. {
  3.     int cBytes = src.length;

  4.  
  5.     // 新建一个(由中间代码编写的)方法,它接受两个byte[]类型的参数,即src和dst。
  6.     DynamicMethod dynameth = new DynamicMethod("Go", typeof(void), new Type[] { typeof(byte[]), typeof(byte[]) }, GetType());
  7.     
  8.     // 用于生成中间代码的类
  9.     ILGenerator generator = dynameth.GetILGenerator();
  10.  
  11.     // 定义局部变量
  12.     generator.DeclareLocal(typeof(int)); // 索引0 = iDst
  13.     generator.DeclareLocal(typeof(double)); // 索引1 = pixelsAccum
  14.     generator.DeclareLocal(typeof(double)); // 索引2 = filterAccum

  15.  
  16.     // 初始化iDst
  17.     generator.Emit(OpCodes.Ldc_I4_0); // 把常数0压入栈中,它是四字节的整型值
  18.      generator.Emit(OpCodes.Stloc_0); // 把栈顶元素(整型值0)赋给第0个局部变量(iDst),并弹出

  19.  
  20.     // labelTop:
  21.     Label labelTop = generator.DefineLabel(); // 创建一个用于跳转指令的label
  22.     generator.MarkLabel(labelTop); // 标记labelTop
  23.  
  24.     // 初始化pixelAccum和filterAccum
  25.     generator.Emit(OpCodes.Ldc_R8, 0.0); // 把一个8字节的实数0.0压入栈顶
  26.     generator.Emit(OpCodes.Dup); // 把栈顶元素(0.0)复制一个压入栈顶
  27.     generator.Emit(OpCodes.Stloc_1); // 把栈顶元素(0.0)赋给第1个局部变量(pixelsAccum),并弹出
  28.     generator.Emit(OpCodes.Stloc_2); // 把栈顶元素(0.0)赋给第2个局部变量(filterAccum),并弹出

  29.  
  30.     // 这里通过循环生成特定长度的中间代码
  31.     for (int iFilter = 0; iFilter < filter.Length; iFilter++)
  32.     {
  33.         // 如果过滤阵列的当前元素为0,则不生成中间代码,即不做任何事
  34.          if (filter[iFlter] == 0)
  35.             continue;
  36.  
  37.         // 下面的运算结果可以直接写入到中间代码中
  38.          int xFilter = iFilter % cxFilter;
  39.         int yFilter = iFilter / cxFilter;
  40.         int offset = stride * (yFilter - cyFilter / 2) + bytesPerPixels * (xFilter - cxFilter / 2);
  41.     
  42.         generator.Emit(OpCodes.Ldarg_0); // 把第0个参数(src数组)压入到栈顶
  43.  
  44.          // 下列的label将用于if...else if...分支
  45.          Label labelLessThanZero = generator.DefineLabel();
  46.         Label labelGreaterThan = generator.DefineLabel();
  47.         Label labelLoopBottom = generator.DefineLabel();
  48.  
  49.         // 计算iDst + offset
  50.         generator.Emit(OpCodes.Ldloc_0); // 将iDst压入栈
  51.          generator.Emit(OpCodes.Ldc_I4, offset); // 将offset的值压入栈
  52.          generator.Emit(OpCodes.Add); // 把iDst与offset从栈中弹出,并把它们相加,得到的和压入到栈顶
  53.          generator.Emit(OpCodes.Dup); // 把相加的和复制两次
  54.          generator.Emit(OpCodes.Dup);
  55.   
  56.         // if (iDst + offset >= 0)
  57.         generator.Emit(OpCodes.Ldc_I4_0); // 把整型值0压入栈顶
  58.          generator.Emit(OpCodes.Blt_S, labelLessThanZero); 将栈顶的两个元素弹出并进行比较,如果前者小于后者(iDst+offset < 0)则跳转到labelLessThanZero的位置。Blt_S=Branch-if-Less-Than,S表示跳转的偏移量小于256个字节
  59.  
  60.          // if (iDst + offset < cBytes)
  61.         generator.Emit(OpCodes.Ldc_I4, cBytes);
  62.         generator.Emit(Opcodes.Bge_S, labelGreaterThan); // 将栈顶的两个元素弹出并进行比较,如果前者大于等于后者(iDst+offset >= cBytes)则跳转到labelGraterThan的位置。
  63.  
  64.          // (double)src[iDst+offset]
  65.         generator.Emit(OpCodes.Ldelem_U1); // 当前栈顶元素为iDst + offset的值,第二个元素为src数组。将这两个元素弹出,并取src[iDst+offset]的Unsingned的1字节的值压入栈顶。
  66.         generator.Emit(OpCodes.Conv_R8); // 把栈顶元素转换并替换为一个8字节的浮点数。
  67.  
  68.         if (filter[iFilter] == 1)
  69.         {
  70.             // 乘数为1,保持栈顶的值
  71.         }
  72.         else if (filter[iFilter] == -1)
  73.         {
  74.             // 乘数为-1,栈顶值取负数
  75.         }
  76.         else
  77.         {
  78.             // (double)src[iDst+offset] * filter[iFilter]
  79.             generator.Emit(OpCodes.Ldc_R8, filter[iFilter]);
  80.             generator.Emit(OpCodes.Mul);
  81.         }
  82.  
  83.         // pixelsAccum += src[iDst+offset] * filter[iFilter];
  84.         generator.Emit(OpCodes.Ldloc_1); // 把局部变量pixelsAccum压入栈顶
  85.         generator.Emit(OpCodes.Add); // 把pixelsAccum与(double)src[iDst+offset] * filter[iFilter]从栈中弹出并相加,把和压入栈顶
  86.         generator.Emit(OpCodes.Stloc_1); // 把上面加法操作的和赋给局部变量pixelsAccum。

  87.  
  88.         // filterAccum += filter[iFilter];
  89.         generator.Emit(OpCodes.Ldc_R8, filter[iFilter]); // 把filter[iFilter]压入栈顶
  90.         generator.Emit(OpCodes.Ldloc_2); // 把局部变量filterAccum压入栈顶
  91.         generator.Emit(OpCodes.Add); // 令filterAccum与filter[iFilter]相加
  92.         generator.Emit(OpCodes.Stloc_2); // 把相加的和赋给filterAccum
  93.         generator.Emit(OpCodes.Br, labelLoopBottom); // 跳转到labelLoopBottom的位置,即内循环的末尾,对应着FilterMethodCS的24行
  94.  
  95.         // if...else if的结束标签以及内循环末尾的标签
  96.         generator.MarkLabel(labelLessThanZero); // labelLessThanZero:
  97.         generator.Emit(OpCodes.Pop); // 当iDst+offset < 0时,栈顶用于与cByte比较的iDst+offset的值并未被使用,需要被清理。
  98.         generator.MarkLabel(labelGreaterThan); // labelGreaterThan:
  99.         generator.Emit(OpCodes.Pop); // 当iDst+offset < 0 或 iDst+offset >= cBytes时,清理用于数组取值的栈顶的iDst+offset值。
  100.         generator.Emit(OpCodes.Pop); // 清理栈顶的src数组
  101.         generator.MarkLabel(labelLoopBottom);

  102.  

  103.         generator.Emit(OpCodes.Ldarg_1); // dst数组压栈
  104.         generator.Emit(OpCodes.Ldloc_0); // iDst压栈
  105.  
  106.         Label labelSkipDivide = generator.DefineLabel();
  107.        Label labelCopyQutient = generator.DefineLabel();
  108.        Label labelBlack = generator.DefineLabel();
  109.        Label labelWhite = generator.DefineLabel();
  110.        Label labelDone = generator.DefineLabel();

  111.        generator.Emit(OpCodes.Ldloc_1); // pixelsAccum
  112.        generator.Emit(OpCodes.Ldloc_2); // filterAccum
  113.        generator.Emit(OpCodes.Dup); // 复制filterAccum
  114.        generator.Emit(OpCodes.Ldc_R8, 0.0); 将0入栈
  115.        generator.Emit(OpCodes.Beq_S, labelSkipDivide); // if (filterAccum != 0)

  116.        generator.Emit(OpCodes.Div); // pixelsAccum / filterAccum
  117.        generator.Emit(OpCodes.Br_S, labelCopyQuotient); // 跳转到labelCopyQuotient

  118.        generator.MarkLabel(labelSkipDivide);
  119.        generator.Emit(OpCodes.Pop); // 弹出filterAccum

  120.        generator.MarkLabel(labelCopyQuotient);
  121.        generator.Emit(OpCodes.Dup); // 当filterAccum不为0时,栈顶元素为pixelsAccum,当filterAccum为0时,栈顶元素为pixelsAccum/filterAccum。复制栈顶元素
  122.         generator.Emit(OpCodes.Dup); // 再复制一次。

  123.         generator.Emit(OpCodes.Ldc_R8, 0.0);
  124.        generator.Emit(OpCodes.Blt_S, labelBlack); // if (pixelsAccum < 0)

  125.        generator.Emit(OpCodes.Ldc_R8, 255.0);
  126.        generator.Emit(OpCodes.Bgt_S, labelWhite); // if (pixelsAccum > 255.0)

  127.        // if (pixcelsAccum >= 0 && pixcelsAccum <= 255)
  128.        generator.Emit(OpCodes.Conv_U1); // 将pixelsAccum转换为unsigned的1字节的值
  129.        generator.Emit(OpCodes.Br_S, labelDone);

  130.  
  131.        generator.MarkLabel(labelBlack);
  132.        generator.Emit(OpCodes.Pop);
  133.        generator.Emit(OpCodes.Pop);
  134.        generator.Emit(OpCodes.Ldc_I4_S, 0); // pixelsAccum = 0
  135.        generator.Emit(OpCodes.Br_S, labelDone);
  136.   
  137.        generator.MarkLabel(labelWhite);
  138.        generator.Emit(OpCodes.Pop);
  139.        generator.Emit(OpCodes.Ldc_I4_S, 255); // pixelsAccum = 255
  140.  
  141.        generator.MarkLabel(labelDone);
  142.        generator.Emit(OpCodes.Stelem_I1); // dst[iDst] = pixelsAccum

  143.        generator.Emit(OpCodes.Ldloc_0); // 将iDst入栈
  144.        generator.Emit(OpCodes.Ldc_I4_1); // 将1入栈
  145.        generator.Emit(OpCodes.Add); // iDst + 1入栈
  146.        generator.Emit(OpCodes.Dup); // 复制iDst + 1
  147.        generator.Emit(OpCodes.Stloc_0); // iDst = iDst +1
  148.        generator.Emit(OpCodes.Ldc_I4, cBytes); // cBytes入栈
  149.        generator.Emit(OpCodes.Blt, labelTop); // 如果iDst < cBytes,开始下一次循环。

  150.  
  151.        generator.Emit(OpCodes.Ret); // 返回
  152.  

  153.        // 调用生成的中间代码函数
  154.        dynameth.Invoke(this, new object[] { src, dst });
  155.    }
  156. }

上面的代码的运行时间是之前FilterMethodCS的1/4。Charles认为虽然代码的外观不算漂亮,但对于一个算法来说,有办法能使它的耗时降到原来的1/4的话,那是非常漂亮的。

 

:1953年生,美国程序员,微软最有价值专家。代表作有《Programming Windows》等。

 

阅读(1325) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~