首页 > 代码库 > 汇编学习:二维数组遍历

汇编学习:二维数组遍历

作为正式接触汇编的开篇,本文将研究二维数组的遍历问题。在图像处理中,通常需要遍历图像像素(即二维数组)。下面给出三个版本的遍历函数,并研究他们的汇编代码(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]  //加载高度到寄存器eax003013EF  mov         edi,dword ptr [esp+48h]  //加载宽度到寄存器edi003013F3  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]  //源地址-目的地址,偏移量保存在寄存器esi0030141E  mov         eax,ecx                  //目的数组起始地址加载到寄存器eax00301420  or          dl,0FFh                 //将寄存器dl置为25500301423  sub         dl,byte ptr [esi+eax]    //255减去源数组中的值(源数组元素地址=目的地址+偏移量),差保存在寄存器dl00301426  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]  //将宽度加载到寄存器edi00301431  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]  //将高度加载待寄存器edx00FA13E5  mov         eax,dword ptr [esp+40h]  //将宽度加载到寄存器eax00FA13E9  mov         edi,eax                 //将宽度加载到寄存器edi00FA13EB  cmp         edx,ebx                    //测试高度是否小于等于0 00FA13ED  jle         main+215h (0FA1435h)         //若是,转到0FA1435h00FA13EF  mov         esi,dword ptr [esp+44h]  //将源数组地址加载到寄存器esi00FA13F3  mov         ecx,dword ptr [esp+7Ch]  //将目的数组地址加载到寄存器ecx00FA13F7  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                    //测试宽度是否小于等于000FA1405  jle         main+203h (0FA1423h)     //若是,转到0FA1423h00FA1407  mov         eax,ecx                 //将目的数组起始地址加载到寄存器eax00FA1409  sub         esi,ecx                 //源地址-目的地址,偏移量保存在寄存器esi00FA140B  jmp         main+1F0h (0FA1410h)         //跳转至0FA1410h00FA140D  lea         ecx,[ecx]                 //无意义指令?00FA1410  or          dl,0FFh                    //将寄存器的dl置25500FA1413  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]  //加载高度到寄存器ecx000213DF  mov         edx,dword ptr [esp+38h]  //加载宽度到寄存器edx000213E3  mov         eax,dword ptr [esp+3Ch]  //加载源数组地址到寄存器eax000213E7  mov         esi,dword ptr [esp+74h]  //加载目的数组地址到寄存器esi000213EB  cmp         ecx,ebx                 //测试宽度是否小于等于0000213ED  jle         main+1F2h (21412h)         //若是跳转到21412h处000213EF  mov         dword ptr [esp+14h],ecx  //将高度保存到地址[esp+14h]的内存处000213F3  cmp         edx,ebx                 //测试高度是否小于等于0000213F5  jle         main+1ECh (2140Ch)         //若是跳转到2140Ch处000213F7  mov         edi,edx                    //将宽度1024加载到寄存器dei ,对应内层循环的循环次数000213F9  lea         esp,[esp]                 //无意义的操作?相当于mov esp,esp00021400  or          cl,0FFh                 //将寄存器cl置为25500021403  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

技术分享

技术分享

技术分享

汇编学习:二维数组遍历