首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > C++ >

由一篇帖子引起,自己写了点东西,望各位不吝指点。该如何解决

2012-05-04 
由一篇帖子引起,自己写了点东西,望各位不吝指点。昨天在CSDN上看到这样一个帖子C/C++ code// 普通青年std::

由一篇帖子引起,自己写了点东西,望各位不吝指点。
昨天在CSDN上看到这样一个帖子

C/C++ code
// 普通青年std::swap(a, b);// 文艺青年void swap(int* a, int* b){  int t = *a;  *a = *b;  *b = t;}// 2B青年void swap(int* a, int* b){*a^=*b^=*a^=*b;}



首先庆幸一下自己属于文艺青年的范畴,嘿嘿
然后把这个给别人分享了一下,别人告诉我,第二种方法比第三种方法快,我第一反应就是,怎么可能,第三种用的是位

运算,应该会比普通的方法快。
我不相信,自己测试了一下,果然第二种方法比第三种方法快,时间差不多有个4:5的关系,然后我就很好奇,逆了一下

这两段代码,得出了下面的一个结论
下面的代码是第二个的汇编代码,我详细注释了一下

Assembly code
var_8= dword ptr -8var_4= dword ptr -4;定义两个变量5和4mov     [ebp+var_4], 5mov     [ebp+var_8], 4;向函数传入两个地址lea     eax, [ebp+var_8]    ;取4的偏移地址mov     [esp+4], eax        ;将这个地址进栈lea     eax, [ebp+var_4]    ;取5的偏移地址mov     [esp], eax        ;将这个地址进栈call    sub_401390        ;调用普通方式的swap的函数sub_401390 proc near        ;实现过程var_4= dword ptr -4arg_0= dword ptr  8arg_4= dword ptr  0Chpush    ebp            ;保存ebpmov     ebp, esp        ;将esp赋值给ebpsub     esp, 4            ;栈顶指针减4,开辟一个空间,就是上述代码中的tmp的空间mov     eax, [ebp+arg_0]    ;将5的地址赋值给eaxmov     eax, [eax]        ;将eax指向的内容赋值给eax,取*a值的过程mov     [ebp+var_4], eax    ;将*a的值赋值给新开辟的空间,即tmpmov     edx, [ebp+arg_0]    ;用edx将4的地址保存mov     eax, [ebp+arg_4]    ;将4的地址赋值给eaxmov     eax, [eax]        ;将eax指向的内容赋值给eax,取*b值的过程mov     [edx], eax        ;将eax赋值给edx指向的内容,就*b = *a的过程mov     edx, [ebp+arg_4]    ;将4赋值给edxmov     eax, [ebp+var_4]    ;将tmp赋值给eaxmov     [edx], eax        ;将eax赋值给edx指向的空间,即*a = tmp的过程leaveretnsub_401390 endp


下面的是第三个函数的汇编代码

Assembly code
;同上mov     [ebp+var_4], 5mov     [ebp+var_8], 4;同上lea     eax, [ebp+var_8]mov     [esp+4], eaxlea     eax, [ebp+var_4]mov     [esp], eaxcall    sub_401390sub_401390 proc nearvar_10= dword ptr -10harg_0= dword ptr  8arg_4= dword ptr  0Chpush    ebp            ;保存ebpmov     ebp, esp        ;将esp赋值给ebp;下面保存三个寄存器保存,以便能下面使用这三个寄存器push    edi        push    esipush    ebxsub     esp, 4            ;开辟一个空间mov     edi, [ebp+arg_0]    ;取值5赋值给edimov     eax, [ebp+arg_0]    ;取值5赋值给eaxmov     [ebp+var_10], eax    ;将eax赋值给ebp-10的地方mov     ebx, [ebp+arg_4]    ;将4地址赋值给ebxmov     esi, [ebp+arg_4]    ;将4地址赋值给esimov     ecx, [ebp+arg_0]    ;将5地址赋值给ecxmov     edx, [ebp+arg_0]    ;将5地址赋值给edxmov     eax, [ebp+arg_4]    ;将4地址赋值给eaxmov     eax, [eax]        ;将eax指向的内容赋值给eax,就是eax = 4xor     eax, [edx]        ;将edx指向的内容与eax进行按为异或,就是4^5的过程,结果eax=(100)^(101)=001 mov     [ecx], eax        ;将eax赋值给ecx指向的空间,此时ecx = 1mov     eax, [ecx]        ;将ecx指向的空间赋值给eaxxor     eax, [esi]        ;将esi指向的空间和eax进行异或,即4^1=(100)^(001)=101=5;mov     [ebx], eax        ;将eax赋值给[ebx],就是4的地址mov     eax, [ebx]        ;将[ebx]赋值给eaxmov     edx, [ebp+var_10]    ;将最开始的eax赋值给edx,即5xor     eax, [edx]        ;5与eax进行异或,即5^1=(101)^(001)=(100)=4mov     [edi], eax        ;保存到[edi]中,edi最开始是5的地址,这样就保证了交换add     esp, 4            ;释放申请的空间;下面是恢复ebx,esi,edi,ebp,清除现场pop     ebx            pop     esipop     edipop     ebpretnsub_401390 endp


明显可以看到第二个比第三个的汇编代码少,差不多有4:5的比例,说一下第三段代码,虽然是用位运算做的,可是中间

还有很多的细节,而且同样申请空间去保存中间变量了,其中用的寄存器也比第二个多,时间基本上花费在保存中间变量

上了,相比第二个就不太好了,关于第一段代码,我就不解释了。
想起了赵老师经常说的一句话,
眼过千遍不如手过一遍!
书看千行不如手敲一行!
手敲千行不如单步一行!
单步源代码千行不如单步对应汇编一行!
想想非常有道理,实践是检验真理的唯一标准啊。
学汇编不久,肯定有不妥的地方,写的不好或不对的地方望各位大牛指点,非常感谢

[解决办法]
正式的代码中写第三种的, 可以去离开编码去文艺了.

另:代码的长度不能说明任何问题.包括汇编
[解决办法]
第三种写法是在汇编语言里的技巧,能够少用一个寄存器。如果放到C里面,好像编译器没有聪明到知道如何优化这种方式的程度。


Assembly code
xor eax,ebxxor ebx,eaxxor eax,ebx
[解决办法]
// 2B青年
void swap(int* a, int* b){*a^=*b^=*a^=*b;}
这种严重依赖于编译器的优化
写这种语法糖的代码是不可取的
[解决办法]
a和b是指针,如果a=b,或者a和b指向的int有重叠都不行
[解决办法]
C/C++ code
#include <stdio.h>#define SWAP(a,b) do ((&(a))!=(&(b)))?((a)^=(b)^=(a)^=(b)):((a)=(a)); while (0)char   *p1="1" ,*p2="2" ;char    c1=1   , c2=2   ;short   s1=1   , s2=2   ;int     i1=1   , i2=2   ;__int64 I1=1i64, I2=2i64;float   f1=1.0f, f2=2.0f;double  d1=1.0 , d2=2.0 ;void main() {    SWAP((int)p1,(int)p2);                printf("char *     %5s,   %5s\n",p1,p2);    SWAP(c1,c2);                          printf("char       %5d,   %5d\n",c1,c2);    SWAP(s1,s2);                          printf("short      %5d,   %5d\n",s1,s2);    SWAP(i1,i2);                          printf("int        %5d,   %5d\n",i1,i2);    SWAP(I1,I2);                          printf("__int64 %5I64d,%5I64d\n",I1,I2);    SWAP(*(int     *)&f1,*(int     *)&f2);printf("float      %5g,   %5g\n",f1,f2);    SWAP(*(__int64 *)&d1,*(__int64 *)&d2);printf("double    %5lg,  %5lg\n",d1,d2);    SWAP(c1,c1);    printf("%d\n",c1);}//char *         2,       1//char           2,       1//short          2,       1//int            2,       1//__int64     2,    1//float          2,       1//double        2,      1//2
[解决办法]
C/C++ code
void swap(int *a, int *b){    __asm    {        mov eax, [ebp+08h]        push dword ptr [eax]        mov ecx, [ebp+0ch]        push dword ptr [ecx]        pop dword ptr [eax]        pop dword ptr [ecx]    }}
[解决办法]
release 版本, 2B 青年的写法, 还是很不错的。
intel parallel studio

C/C++ code
#include <stdio.h>int     i1=1   , i2=2   ;// 2B青年void swap(int* a, int* b){*a^=*b^=*a^=*b;} void main() {        printf("%d\n",i1);    printf("%d\n",i2);    swap(&i1,&i2);    printf("%d\n",i1);    printf("%d\n",i2);}}
[解决办法]
函数的结构还是被优化得一塌糊涂
这样的看法应该是合理的
算法注重点应该是渐进性态,比如把O(n*n)改成O(logn),现在的编译器都是很可以的,所以c语言编程在速度上不应该在swap2b,swapwy这样O(1)上计较,计较也没用,c代码完全是抽象,和生成的机器码会差很远,只是最终效果一样

对于void swap2B(int* a, int* b){*a^=*b^=*a^=*b;} 
编译器完全可以预测输入的a,b是否重叠,如果可预测不重叠,就等价于交换,如果预测a=b,那么都成0,否则产生另一套保守的代码

真要速度,用汇编去

热点排行