《代码之美》——代码只不过是一种聪明的数据

引言

数据只不过是一种笨程序。   

                       ——Bill Gosper

    今天看了《代码之美》的第八章--图像处理中的即时代码生成。感觉印象深刻。倒不是程序代码写得有多么牛,而是他程序中透露的那样一种思想,这才是这一章的亮点。

    这学期刚学了编译原理,也学了计算机组成原理。感觉这篇文章很好得将这样两个学科结合到了一起。尤其对编译原理有了更深层次的理解,不会像以往那样觉得编译原理就是专门用来写编译器的,就是用来将C,C++,Java等语言编译成目标代码的软件。看了Charles Petzold的文章,发现编译原理原来在程序中可以这么用。编译居然还映射出了数据和程序之间的千丝万缕的联系。

理解

    其实在组成原理里面我们已经知道了,程序和数据其实是一样的,都是一串二进制数字,而且对于冯诺依曼架构而言,也都是存储在内存中。那么计算机是如何区分,那些是程序哪些是数据的呢?这个就要求微程序的控制,这些都是在计算机硬件中设定好的。哪些时刻总线上存放的是数据,哪些时刻总线上存放的是程序,这些计算机都可以预先知道。从而达到将程序与数据的分离。

image

    相信学过的对这个图应该不陌生吧。数据和我们写的程序都会被存放在内存中,由操作控制器的操作控制信号来控制读取。于是我们发现程序与数据其实就是一个时序上的问题,如果时序提前了或者滞后了。那么我们的数据将会变成程序,这就是为什么会出现一种病毒,他的工作机制就是一直往数据段里面写内容,直达内容溢出,然后进入程序段,这个时候如果计算机没有溢出保护的话,继续写入的内容就会被计算机当做程序处理,而不是数据。而这些程序恰恰是那些病毒程序。

实用的例子

    在第八章中,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

posted @ 2011-02-01 11:34  pangliang  阅读(734)  评论(1编辑  收藏  举报