《代码之美》——代码只不过是一种聪明的数据
引言
数据只不过是一种笨程序。
——Bill Gosper
今天看了《代码之美》的第八章--图像处理中的即时代码生成。感觉印象深刻。倒不是程序代码写得有多么牛,而是他程序中透露的那样一种思想,这才是这一章的亮点。
这学期刚学了编译原理,也学了计算机组成原理。感觉这篇文章很好得将这样两个学科结合到了一起。尤其对编译原理有了更深层次的理解,不会像以往那样觉得编译原理就是专门用来写编译器的,就是用来将C,C++,Java等语言编译成目标代码的软件。看了Charles Petzold的文章,发现编译原理原来在程序中可以这么用。编译居然还映射出了数据和程序之间的千丝万缕的联系。
理解
其实在组成原理里面我们已经知道了,程序和数据其实是一样的,都是一串二进制数字,而且对于冯诺依曼架构而言,也都是存储在内存中。那么计算机是如何区分,那些是程序哪些是数据的呢?这个就要求微程序的控制,这些都是在计算机硬件中设定好的。哪些时刻总线上存放的是数据,哪些时刻总线上存放的是程序,这些计算机都可以预先知道。从而达到将程序与数据的分离。
相信学过的对这个图应该不陌生吧。数据和我们写的程序都会被存放在内存中,由操作控制器的操作控制信号来控制读取。于是我们发现程序与数据其实就是一个时序上的问题,如果时序提前了或者滞后了。那么我们的数据将会变成程序,这就是为什么会出现一种病毒,他的工作机制就是一直往数据段里面写内容,直达内容溢出,然后进入程序段,这个时候如果计算机没有溢出保护的话,继续写入的内容就会被计算机当做程序处理,而不是数据。而这些程序恰恰是那些病毒程序。
实用的例子
在第八章中,Charles Petzold给出了一个特别有意思的例子。是关于图像处理的。刚好我也正在研究这方面所以也特别关注。图像处理现在最棘手的要数图像处理的效率问题,面对成千上万的像素任何一个小小的操作都会影响大局。就像当今的中国,那两道算术题。我们也可以说,任何一个小小的操作乘以一百万都是效率上的大问题。
对于图像的滤波操作更是如此,这个操作总是针对整张图像的。然而滤波矩阵数量繁多,我们总是希望一劳永逸,于是总会偏向于写那些泛化的方法。就像我,还写了一个Filter的类来构造Filter,并且当做参数传入。为了达到通用,我们不得不为Filter写上这样一个循环:
public void FilterMethodCS(byte[] src, byte[] dst, int stride, int bytesPerPixel) { int cBytes = src.Length; int cFilter = filter.Length; for (int iDst = 0; iDst < cBytes; iDst++) { double pixelsAccum = 0; double filterAccum = 0; for (int iFilter = 0; iFilter < cFilter; iFilter++) { int yFilter = iFilter / cyFilter; int xFilter = iFilter % cxFilter; int iSrc = iDst + stride * (yFilter - cyFilter / 2) + bytesPerPixel * (xFilter - cxFilter / 2); if (iSrc >= 0 && iSrc < cBytes) { pixelsAccum += filter[iFilter] * src[iSrc]; filterAccum += filter[iFilter]; } } if (filterAccum != 0) pixelsAccum /= filterAccum; dst[iDst] = pixelsAccum < 0 ? (byte)0 : (pixelsAccum > 255 ? (byte)255 : (byte)pixelsAccum); } }
就是第二个For循环。这样我们可以针对所有的滤波矩阵通用这段代码。
然而你实际试试就知道这样的效率是不高的。里面有大量的无用的操作,例如如果滤波器中的莫一项为0或者1,是不需要乘法的。但这样的无用操作在我们平时的代码中是很常见的,原因很简单,CPU速度快,这样根本不会影响什么。但是图像处理就不一样了。面对百万级的基数,你不得不节约指令条数。于是我们引入了编译原理动态生成可执行的代码,将我们需要的Filter硬编码化,以提高效率。
经常会有人说C#效率低,那是因为C#泛化弄得太优秀了。呵呵。就是他考虑到的比你用到的东西多多了,以至于效率的降低。但是如果能动态得生成所需要的代码,针对特殊情况特殊优化代码,那么其实C#的效率还是挺高的。看下面这段代码:
public void FilterMethodIL(byte[] src, byte[] dst, int stride, int bytesPerPixel) { int cBytes = src.Length; DynamicMethod dynameth = new DynamicMethod("Go", typeof(void), new Type[] { typeof(byte[]), typeof(byte[]) }, GetType()); ILGenerator generator = dynameth.GetILGenerator(); //声明局部变量 generator.DeclareLocal(typeof(int)); //索引0 iDst generator.DeclareLocal(typeof(double)); //索引1 pixelsAccum generator.DeclareLocal(typeof(double)); //索引2 filterAccum //局部变量初始化 generator.Emit(OpCodes.Ldc_I4_0); //将整数0存入求值栈 generator.Emit(OpCodes.Stloc_0); //将求值栈第一个元素放入第一个变量iDst中 //循环头标记 Label labelTop = generator.DefineLabel(); generator.MarkLabel(labelTop); generator.Emit(OpCodes.Ldc_R8, 0.0); //将实数0.0存入求值栈 generator.Emit(OpCodes.Dup); //复制一份 generator.Emit(OpCodes.Stloc_1); //初始化pixelsAccum generator.Emit(OpCodes.Stloc_2); //初始化filterAccum //构造filter运算 for (int iFilter = 0; iFilter < filter.Length; iFilter++) { if (filter[iFilter] == 0) { continue; } int xFilter = iFilter % cxFilter; int yFilter = iFilter / cxFilter; int offset = stride * (yFilter - cyFilter / 2) + bytesPerPixel * (xFilter - cxFilter / 2); //将src数组的引用压入求值栈 generator.Emit(OpCodes.Ldarg_0); //三个标记 Label labelLessThanZero = generator.DefineLabel(); Label labelGreaterThan = generator.DefineLabel(); Label labelLoopBottom = generator.DefineLabel(); generator.Emit(OpCodes.Ldloc_0); //iDec局部变量入栈 generator.Emit(OpCodes.Ldc_I4, offset); //将offset入栈 generator.Emit(OpCodes.Add); //des下标与offset相加 generator.Emit(OpCodes.Dup); //复制两次 generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Ldc_I4_0); //0入栈 generator.Emit(OpCodes.Blt_S, labelLessThanZero);//比较下标与0大小 generator.Emit(OpCodes.Ldc_I4, cBytes); //cBytes入栈 generator.Emit(OpCodes.Bge_S, labelGreaterThan);//比较下标与总字节数大小 generator.Emit(OpCodes.Ldelem_U1); //通过下标访问数组 generator.Emit(OpCodes.Conv_R8); //将取回的值转换成实数 if (filter[iFilter] == 1) { //已在栈中,不做处理 } else if (filter[iFilter] == -1) { generator.Emit(OpCodes.Neg); //快速求负 } else { generator.Emit(OpCodes.Ldc_R8, filter[iFilter]); //filter[iFilter]入栈 generator.Emit(OpCodes.Mul); //求乘积 } //pixelsAccum加上算出的值 generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Add); generator.Emit(OpCodes.Stloc_1); //filterAccum加上filter对应项的值 generator.Emit(OpCodes.Ldc_R8, filter[iFilter]); generator.Emit(OpCodes.Ldloc_2); generator.Emit(OpCodes.Add); generator.Emit(OpCodes.Stloc_2); generator.Emit(OpCodes.Br, labelLoopBottom);//跳转到结束 //小于0处理程序入口 generator.MarkLabel(labelLessThanZero); generator.Emit(OpCodes.Pop); //大于最大像素数处理程序入口 generator.MarkLabel(labelGreaterThan); generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Pop); //循环尾部 generator.MarkLabel(labelLoopBottom); } generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Ldloc_0); //定义五个标签 Label labelSkipDivide = generator.DefineLabel(); Label labelCopyQuotient = generator.DefineLabel(); Label labelBlack = generator.DefineLabel(); Label labelWhite = generator.DefineLabel(); Label labelDone = generator.DefineLabel(); //计算pixelsAccum/filterAccum generator.Emit(OpCodes.Ldloc_1); generator.Emit(OpCodes.Ldloc_2); generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Ldc_R8, 0.0); //包括除数为0判断 generator.Emit(OpCodes.Beq_S, labelSkipDivide); generator.Emit(OpCodes.Div); generator.Emit(OpCodes.Br_S, labelCopyQuotient); //除数为0处理 generator.MarkLabel(labelSkipDivide); generator.Emit(OpCodes.Pop); generator.MarkLabel(labelCopyQuotient); generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Dup); //像素值小于0,置0 generator.Emit(OpCodes.Ldc_R8, 0.0); generator.Emit(OpCodes.Blt_S, labelBlack); //像素值大于255,置255 generator.Emit(OpCodes.Ldc_R8, 255.0); generator.Emit(OpCodes.Bgt_S, labelWhite); generator.Emit(OpCodes.Conv_U1); generator.Emit(OpCodes.Br_S, labelDone); //像素值置0 generator.MarkLabel(labelBlack); generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ldc_I4_S, 0); generator.Emit(OpCodes.Br_S, labelDone); //像素值置255 generator.MarkLabel(labelWhite); generator.Emit(OpCodes.Pop); generator.Emit(OpCodes.Ldc_I4_S, 255); generator.MarkLabel(labelDone); //存入dst数组 generator.Emit(OpCodes.Stelem_I1); //iDst自增 generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldc_I4_1); generator.Emit(OpCodes.Add); generator.Emit(OpCodes.Dup); generator.Emit(OpCodes.Stloc_0); //循环判断条件 generator.Emit(OpCodes.Ldc_I4, cBytes); generator.Emit(OpCodes.Blt, labelTop); generator.Emit(OpCodes.Ret); //调用动态函数 dynameth.Invoke(this, new object[] { src, dst }); }
这个动态函数的生成就调用了.NET下IL语言。在JIT的帮助下,我们可以动态得生成我们所需要的函数,达到函数硬编码的目的,这样不仅效率提高了,而且也不会丢失通用性。
具体的OpCodes可以参考:http://msdn2.microsoft.com/library/system.reflection.emit.opcodes.aspx