• 汇编学习:二维数组遍历


    作为正式接触汇编的开篇,本文将研究二维数组的遍历问题。在图像处理中,通常需要遍历图像像素(即二维数组)。下面给出三个版本的遍历函数,并研究他们的汇编代码(VC2010编译器,x86版,Release模式)。

    (1)在两层循环内每次根据行列索引计算元素位置。

    (2)为了避免在内存循环内的乘法计算,可在每次外层循环中计算好行起始地址,内层循环每次执行++操作。

    (3)将外层循环的乘法操作也去掉,在循环外部先计算好数组的起始地址,内层循环每次执行++操作即可。

    测试程序实现对图像的反相操作(即B=255-A)。我们的直观感觉时他们的访问效率应该逐步提升的,本人之前也是一直用第三种方法来遍历图像像素的。但本次测试发现,效率根本没有提升。究其原因,是编译器做了优化。下面分别给出三个函数以及他们对应的汇编代码(VS中调试—>窗口-->反汇编可查看),并对汇编代码做了注释。

    (1)版本1

    inline void InvimageV1(uchar *A ,uchar *B,int Width,int Height)
    {
        for (int Y=0;Y<Height;Y++)
        {
            for (int X=0;X<Width;X++)
            {
                B[Y*Width+X]=255-A[Y*Width+X];
            }
        }
    }

    汇编:

    003013EB  mov         eax,dword ptr [esp+44h]  //加载高度到寄存器eax
    003013EF  mov         edi,dword ptr [esp+48h]  //加载宽度到寄存器edi
    003013F3  mov         dword ptr [esp+24h],ecx   //将目的数组地址存放到[esp+24h]内存处
    003013F7  mov         ecx,dword ptr [esp+84h]  //将源数组地址加载到寄存器ecx中
    003013FE  mov         dword ptr [esp+1Ch],edi  //将宽度保存到地址[esp+1ch]内存处
    00301402  mov         dword ptr [esp+20h],ecx  //将源数组地址保存到[esp+20h]内存处
    00301406  cmp         eax,ebx                 //测试宽度是否小于等于0(ebx中值为0)
    00301408  jle         main+21Bh (30143Bh)         //若是跳转到30143Bh处
    0030140A  mov         dword ptr [esp+18h],edi  //将宽度数保存在地址[esp+18h]内存处
    0030140E  mov         dword ptr [esp+14h],eax  //将高度保存在地址[esp+14h]内存处
    00301412  cmp         edi,ebx                 //测试宽度是否小于0(ebx中值为0)
    00301414  jle         main+211h (301431h)          //是跳转到301431h处
    00301416  mov         esi,dword ptr [esp+24h]  //将源数组地址加载到寄存器esi 
    0030141A  sub         esi,dword ptr [esp+20h]  //源地址-目的地址,偏移量保存在寄存器esi
    0030141E  mov         eax,ecx                  //目的数组起始地址加载到寄存器eax
    00301420  or          dl,0FFh                 //将寄存器dl置为255
    00301423  sub         dl,byte ptr [esi+eax]    //255减去源数组中的值(源数组元素地址=目的地址+偏移量),差保存在寄存器dl
    00301426  inc         eax                     //eax值加1,对应LinePS++
    00301427  dec         edi                      //edi值减1,控制内层循环次数
    00301428  mov         byte ptr [eax-1],dl       //将差值保存在目的数组对应元素中
    0030142B  jne         main+200h (301420h)         //若edi值非0,继续内层循环
    0030142D  mov         edi,dword ptr [esp+1Ch]  //将宽度加载到寄存器edi
    00301431  add         ecx,dword ptr [esp+18h]   //源数组起始地址加上宽度值,即转向下一行,对应Y*Width+X,每次递增Width,避免乘法
    00301435  dec         dword ptr [esp+14h]      //将高度值减1,控制外层循环
    00301439  jne         main+1F2h (301412h)       //若高度值非零,继续外层循环 

    其中黄色覆盖区域为外层循环,绿色覆盖区域为内层循环。

    (2)版本2

    inline void InvimageV2(uchar *A ,uchar *B,int Width,int Height)
    {
    
        uchar *LinePS,*LinePD;
        for (int Y=0;Y<Height;Y++)
        {
            LinePS=A+Y*Width;
            LinePD=B+Y*Width;
            for (int X=0;X<Width;X++)
            {
                LinePD[X]=255-LinePS[X];
            }
        }
    }

    汇编:

    00FA13E1  mov         edx,dword ptr [esp+3Ch]  //将高度加载待寄存器edx
    00FA13E5  mov         eax,dword ptr [esp+40h]  //将宽度加载到寄存器eax
    00FA13E9  mov         edi,eax                 //将宽度加载到寄存器edi
    00FA13EB  cmp         edx,ebx                    //测试高度是否小于等于0 
    00FA13ED  jle         main+215h (0FA1435h)         //若是,转到0FA1435h
    00FA13EF  mov         esi,dword ptr [esp+44h]  //将源数组地址加载到寄存器esi
    00FA13F3  mov         ecx,dword ptr [esp+7Ch]  //将目的数组地址加载到寄存器ecx
    00FA13F7  mov         dword ptr [esp+18h],eax  //将宽度保存到[esp+18h]内存处 
    00FA13FB  mov         dword ptr [esp+14h],esi  //将源数组地址保存到[esp+14h]内存处
    00FA13FF  mov         dword ptr [esp+10h],edx  //将宽度保存在[esp+10h]内存处
    00FA1403  cmp         edi,ebx                    //测试宽度是否小于等于0
    00FA1405  jle         main+203h (0FA1423h)     //若是,转到0FA1423h
    00FA1407  mov         eax,ecx                 //将目的数组起始地址加载到寄存器eax
    00FA1409  sub         esi,ecx                 //源地址-目的地址,偏移量保存在寄存器esi
    00FA140B  jmp         main+1F0h (0FA1410h)         //跳转至0FA1410h
    00FA140D  lea         ecx,[ecx]                 //无意义指令?
    00FA1410  or          dl,0FFh                    //将寄存器的dl置255
    00FA1413  sub         dl,byte ptr [esi+eax]     //255减原数组元素(源数组元素地址=目的数组元素地址+偏移量)
    00FA1416  inc         eax                        //eax值加1,对应X++
    00FA1417  dec         edi                        //edi值减1,控制内层循环次数
    00FA1418  mov         byte ptr [eax-1],dl        //将差值保存到对应的目的数组元素中 
    00FA141B  jne         main+1F0h (0FA1410h)     //若edi大于0,继续内层循环 
    00FA141D  mov         eax,dword ptr [esp+18h]  //将宽度加载到寄存器eax 
    00FA1421  mov         edi,eax                    //将宽度加载到寄存器edi 
    00FA1423  mov         esi,dword ptr [esp+14h]  //将源数组起始地址加载到寄存器esi 
    00FA1427  add         esi,eax                    //esi(源地址)值加上宽度,即转向下一行,对应LinePS=A+Y*Width,每次递增Width,避免乘法 
    00FA1429  add         ecx,eax                 //ecx(目的地址)值加上宽度,即转向下一行,对应LinePD=B+Y*Width,每次递增Width,避免乘法
    00FA142B  dec         dword ptr [esp+10h]      //将高度值减1,控制外层循环
    00FA142F  mov         dword ptr [esp+14h],esi  //将源数组地址保存到[esp+14h]内存处 
    00FA1433  jne         main+1E3h (0FA1403h)     //若高度值非零,继续外层循环

    其中黄色覆盖区域为外层循环,绿色覆盖区域为内层循环。

     (3)版本3

    inline void InvimageV3(uchar *A ,uchar *B,int Width,int Height)
    {
        uchar *LinePS=A,*LinePD=B;
        for (int Y=0;Y<Height;Y++)
        {
            for (int X=0;X<Width;X++)
            {
                LinePD[0]=255-LinePS[0];
                LinePS++;
                LinePD++;
            }
        }
    }

    汇编:

    000213DB  mov         ecx,dword ptr [esp+34h]  //加载高度到寄存器ecx
    000213DF  mov         edx,dword ptr [esp+38h]  //加载宽度到寄存器edx
    000213E3  mov         eax,dword ptr [esp+3Ch]  //加载源数组地址到寄存器eax
    000213E7  mov         esi,dword ptr [esp+74h]  //加载目的数组地址到寄存器esi
    000213EB  cmp         ecx,ebx                 //测试宽度是否小于等于0
    000213ED  jle         main+1F2h (21412h)         //若是跳转到21412h处
    000213EF  mov         dword ptr [esp+14h],ecx  //将高度保存到地址[esp+14h]的内存处
    000213F3  cmp         edx,ebx                 //测试高度是否小于等于0
    000213F5  jle         main+1ECh (2140Ch)         //若是跳转到2140Ch处
    000213F7  mov         edi,edx                    //将宽度1024加载到寄存器dei ,对应内层循环的循环次数
    000213F9  lea         esp,[esp]                 //无意义的操作?相当于mov esp,esp
    00021400  or          cl,0FFh                 //将寄存器cl置为255
    00021403  sub         cl,byte ptr [eax]         //255减源数组元素,结果存放在寄存器cl中
    00021405  inc         eax                     //eax中值加1,对应LinePS++
    00021406  mov         byte ptr [esi],cl         //将差值存放在目的数组对应元素中
    00021408  inc         esi                     //esi中值加1,对应LinePD++
    00021409  dec         edi                     //edi中值减1,控制内层循环次数
    0002140A  jne         main+1E0h (21400h)         //当edi值非零,继续内层循环
    0002140C  dec         dword ptr [esp+14h]         //将内存地址[esp+14](存放高度)值减1,控制外层循环次数
    00021410  jne         main+1D3h (213F3h)         //当此值仍非零时,继续外层循环

    其中黄色覆盖区域为外层循环,绿色覆盖区域为内层循环。

    可以看出三个版本的内层循环操作上并没有什么差异,Release模式下编译器已经将循环内计算地址中的乘法计算优化成加法,而我们第三个版本的目的正是去掉乘法计算,因此三者执行效率上并没有多大差异。

    下面给出完整的测试代码:

    #include "opencv2/imgproc/imgproc.hpp"
    #include "opencv2/highgui/highgui.hpp"
    #include <iostream>
    using namespace cv;
    using namespace std; 
    inline void InvimageV1(uchar *A ,uchar *B,int Width,int Height)
    {
        for (int Y=0;Y<Height;Y++)
        {
            for (int X=0;X<Width;X++)
            {
                B[Y*Width+X]=255-A[Y*Width+X];
            }
        }
    }
    inline void InvimageV2(uchar *A ,uchar *B,int Width,int Height)
    {
    
        uchar *LinePS,*LinePD;
        for (int Y=0;Y<Height;Y++)
        {
            LinePS=A+Y*Width;
            LinePD=B+Y*Width;
            for (int X=0;X<Width;X++)
            {
                LinePD[X]=255-LinePS[X];
            }
        }
    }
    inline void InvimageV3(uchar *A ,uchar *B,int Width,int Height)
    {
        uchar *LinePS=A,*LinePD=B;
        for (int Y=0;Y<Height;Y++)
        {
            for (int X=0;X<Width;X++)
            {
                LinePD[0]=255-LinePS[0];
                LinePS++;
                LinePD++;
            }
        }
    }
    
    int main()
    {
        Mat src,dst;
        int nWidth,nHeight,iterNum=1000;
        uchar *pSrc,*pDst;
        int64 t1,t2;
        src=imread("1.jpg",0);
        dst = src.clone();
        nWidth=src.cols;
        nHeight=src.rows;        
        pSrc=src.data;
        pDst=dst.data;
        imshow("原始图",src);
        t1=getTickCount();
        for (int i=0;i<iterNum;i++)
        {
            InvimageV1(pSrc ,pDst,nWidth,nHeight);
        }
        t2=getTickCount();
        cout<<"InvimageV1:"<<(t2 - t1)*1000./getTickFrequency()<<"ms"<<endl;
        imshow("InvimageV1",dst);
        
        t1=getTickCount();
        for (int i=0;i<iterNum;i++)
        {
            InvimageV2(pSrc ,pDst,nWidth,nHeight);
        }
        t2=getTickCount();
        cout<<"InvimageV2:"<<(t2 - t1)*1000./getTickFrequency()<<"ms"<<endl;
        imshow("InvimageV2",dst);
    
        t1=getTickCount();
        for (int i=0;i<iterNum;i++)
        {
            InvimageV3(pSrc,pDst,nWidth,nHeight);
        }
        t2=getTickCount();
        cout<<"InvimageV3:"<<(t2 - t1)*1000./getTickFrequency()<<"ms"<<endl;
        imshow("InvimageV3",dst);
        waitKey(0);
    }
    View Code

    
    
  • 相关阅读:
    const 与 readonly知多少
    js中得~~是什么意思/JS按位非(~)运算符与~~运算符的理解分析
    严格模式详解
    Javascript中的prototype和__proto__的联系区别
    提升页面渲染效率
    window.onload=function(){}和$(function(){})的区别
    gulp的安装和配置
    JS的组成部分、引入页面的方法以及命名规范
    js继承的三种实现
    前端模块化
  • 原文地址:https://www.cnblogs.com/luo-peng/p/5937166.html
Copyright © 2020-2023  润新知