Chinaunix首页 | 论坛 | 博客
  • 博客访问: 8102829
  • 博文数量: 159
  • 博客积分: 10424
  • 博客等级: 少将
  • 技术积分: 14615
  • 用 户 组: 普通用户
  • 注册时间: 2010-07-14 12:45
个人简介

啦啦啦~~~

文章分类
文章存档

2015年(5)

2014年(1)

2013年(5)

2012年(10)

2011年(116)

2010年(22)

分类: C/C++

2011-09-08 23:16:07

本文的copyleft归gfree.wind@gmail.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
 

上一篇博文,发现glibc中的strcpy的效率居然比我写出的例子要差,这实在让我感到惊奇。下面看看为什么glibc中的实现,效率会低呢?让我们反汇编两个实现的代码
第一个strcpy,是我写的例子。
  1. char* my_strcpy1(char *dest, const char *src)
  2. {
  3.     char *= dest;
  4.     register char c;
  5.     
  6.     do {
  7.         c = *src++;
  8.         *d++ = c;
  9.     } while ('\0' != c);

  10.     return dest;
  11. }
  12. 它对应的汇编代码如下:
  1. Dump of assembler code for function my_strcpy1:
  2. 0x08048394 <+0>: push %ebp
  3. 0x08048395 <+1>: mov %esp,%ebp
  4. 0x08048397 <+3>: push %ebx
  5. 0x08048398 <+4>: sub $0x10,%esp
  6. 0x0804839b <+7>: mov 0x8(%ebp),%eax
  7. 0x0804839e <+10>: mov %eax,-0x8(%ebp)
  8. 0x080483a1 <+13>: mov 0xc(%ebp),%eax
  9. 0x080483a4 <+16>: movzbl (%eax),%ebx
  10. 0x080483a7 <+19>: addl $0x1,0xc(%ebp)
  11. 0x080483ab <+23>: mov -0x8(%ebp),%eax
  12. 0x080483ae <+26>: mov %bl,(%eax)
  13. 0x080483b0 <+28>: addl $0x1,-0x8(%ebp)
  14. 0x080483b4 <+32>: test %bl,%bl
  15. 0x080483b6 <+34>: jne 0x80483a1
  16. 0x080483b8 <+36>: mov 0x8(%ebp),%eax
  17. 0x080483bb <+39>: add $0x10,%esp
  18. 0x080483be <+42>: pop %ebx
  19. 0x080483bf <+43>: pop %ebp
  20. 0x080483c0 <+44>: ret
  21. End of assembler dump.
红色部分的汇编代码为do {} while循环代码。

glibc中的strcpy的代码如下:
  1. /* Copy SRC to DEST. */
  2. char *
  3. my_strcpy2 (dest, src)
  4.      char *dest;
  5.      const char *src;
  6. {
  7.   register char c;
  8.   char * s = (char *)src;
  9.   const int off = dest - s - 1;

  10.   do
  11.     {
  12.       c = *s++;
  13.       s[off] = c;
  14.     }
  15.   while (!= '\0');

  16.   return dest;
  17. }
对应的汇编代码如下:
  1. Dump of assembler code for function my_strcpy2:
  2. 0x080483c1 <+0>: push %ebp
  3. 0x080483c2 <+1>: mov %esp,%ebp
  4. 0x080483c4 <+3>: push %ebx
  5. 0x080483c5 <+4>: sub $0x10,%esp
  6. 0x080483c8 <+7>: mov 0xc(%ebp),%eax
  7. 0x080483cb <+10>: mov %eax,-0xc(%ebp)
  8. 0x080483ce <+13>: mov 0x8(%ebp),%edx
  9. 0x080483d1 <+16>: mov -0xc(%ebp),%eax
  10. 0x080483d4 <+19>: mov %edx,%ecx
  11. 0x080483d6 <+21>: sub %eax,%ecx
  12. 0x080483d8 <+23>: mov %ecx,%eax
  13. 0x080483da <+25>: sub $0x1,%eax
  14. 0x080483dd <+28>: mov %eax,-0x8(%ebp)
  15. 0x080483e0 <+31>: mov -0xc(%ebp),%eax
  16. 0x080483e3 <+34>: movzbl (%eax),%ebx
  17. 0x080483e6 <+37>: addl $0x1,-0xc(%ebp)
  18. 0x080483ea <+41>: mov -0x8(%ebp),%eax
  19. 0x080483ed <+44>: add -0xc(%ebp),%eax
  20. 0x080483f0 <+47>: mov %bl,(%eax)
  21. 0x080483f2 <+49>: test %bl,%bl
  22. 0x080483f4 <+51>: jne 0x80483e0
  23. 0x080483f6 <+53>: mov 0x8(%ebp),%eax
  24. 0x080483f9 <+56>: add $0x10,%esp
  25. 0x080483fc <+59>: pop %ebx
  26. 0x080483fd <+60>: pop %ebp
  27. 0x080483fe <+61>: ret
  28. End of assembler dump.
这里的红色部分同样是对应的do{}while循环代码。

两个实现对应的汇编代码基本相似,那么是否由循环前面的代码引起的呢。my_strcpy2使用了offset,所以多了一些mov和sub操作。我再次更改了代码,在my_strcpy2中不再计算offset。
  1. #include <stdio.h>
  2. #include <stdlib.h>


  3. char* my_strcpy1(char *dest, const char *src)
  4. {
  5.     char *d = dest;
  6.     register char c;

  7.     do {
  8.         c = *src++;
  9.         *d++ = c;
  10.     } while ('\0' != c);

  11.     return dest;
  12. }


  13. int off;

  14. /* Copy SRC to DEST. */
  15. char *
  16. my_strcpy2 (dest, src)
  17.      char *dest;
  18.      const char *src;
  19. {
  20.   register char c;
  21.   char * s = (char *)src;

  22.   do
  23.     {
  24.       c = *s++;
  25.       s[off] = c;
  26.     }
  27.   while (c != '\0');

  28.   return dest;
  29. }

  30. int main()
  31. {
  32.     const char *str1 = "test1";
  33.     char buf[100];

  34.     off = buf-str1-1;

  35.     int i;
  36.     for (i = 0; i < 10000000; ++i) {
  37.         my_strcpy1(buf, str1);
  38.     }

  39.     return 0;
  40. }
通过使用一个off的全局变量,来省得my_strcpy2的offset的计算。但是结果仍然是my_strcpy1效率跟高。
my_strcpy1的时间约为0.147s,而my_strcpy2的时间为0.220s。再次查看汇编
  1. (gdb) disassemble my_strcpy1
  2. Dump of assembler code for function my_strcpy1:
  3. 0x08048394 <+0>: push %ebp
  4. 0x08048395 <+1>: mov %esp,%ebp
  5. 0x08048397 <+3>: push %ebx
  6. 0x08048398 <+4>: sub $0x10,%esp
  7. 0x0804839b <+7>: mov 0x8(%ebp),%eax
  8. 0x0804839e <+10>: mov %eax,-0x8(%ebp)
  9. 0x080483a1 <+13>: mov 0xc(%ebp),%eax
  10. 0x080483a4 <+16>: movzbl (%eax),%ebx
  11. 0x080483a7 <+19>: addl $0x1,0xc(%ebp)
  12. 0x080483ab <+23>: mov -0x8(%ebp),%eax
  13. 0x080483ae <+26>: mov %bl,(%eax)
  14. 0x080483b0 <+28>: addl $0x1,-0x8(%ebp)
  15. 0x080483b4 <+32>: test %bl,%bl
  16. 0x080483b6 <+34>: jne 0x80483a1
  17. 0x080483b8 <+36>: mov 0x8(%ebp),%eax
  18. 0x080483bb <+39>: add $0x10,%esp
  19. 0x080483be <+42>: pop %ebx
  20. 0x080483bf <+43>: pop %ebp
  21. 0x080483c0 <+44>: ret
  22. End of assembler dump.
  23. (gdb) disassemble my_strcpy2
  24. Dump of assembler code for function my_strcpy2:
  25. 0x080483c1 <+0>: push %ebp
  26. 0x080483c2 <+1>: mov %esp,%ebp
  27. 0x080483c4 <+3>: push %ebx
  28. 0x080483c5 <+4>: sub $0x10,%esp
  29. 0x080483c8 <+7>: mov 0xc(%ebp),%eax
  30. 0x080483cb <+10>: mov %eax,-0x8(%ebp)
  31. 0x080483ce <+13>: mov -0x8(%ebp),%eax
  32. 0x080483d1 <+16>: movzbl (%eax),%ebx
  33. 0x080483d4 <+19>: addl $0x1,-0x8(%ebp)
  34. 0x080483d8 <+23>: mov 0x80496bc,%eax
  35. 0x080483dd <+28>: add -0x8(%ebp),%eax
  36. 0x080483e0 <+31>: mov %bl,(%eax)
  37. 0x080483e2 <+33>: test %bl,%bl
  38. 0x080483e4 <+35>: jne 0x80483ce
  39. 0x080483e6 <+37>: mov 0x8(%ebp),%eax
  40. 0x080483e9 <+40>: add $0x10,%esp
  41. 0x080483ec <+43>: pop %ebx
  42. 0x080483ed <+44>: pop %ebp
  43. 0x080483ee <+45>: ret
  44. End of assembler dump.
现在效率仍然有区别,那么看来还是循环处出的问题。时间又晚了,下次再继续研究。

阅读(7224) | 评论(10) | 转发(4) |
给主人留下些什么吧!~~

GFree_Wind2011-09-09 23:17:10

Rainyzzj: 期待答案。但是我很好奇,你是怎么测试的呢?直接用汇编写两句这样的做循环然后测试?还是怎么弄?.....
嵌入式汇编。将这两句嵌入到C中。

中秋要去旅游,博客会停5天哦。。。

Rainyzzj2011-09-09 16:37:54

GFree_Wind: 已经测试过了。不是这两句的问题。

而且这两句都有间接寻址,所以应该不是间接寻址的问题.....
期待答案。但是我很好奇,你是怎么测试的呢?直接用汇编写两句这样的做循环然后测试?还是怎么弄?

GFree_Wind2011-09-09 14:01:45

Rainyzzj: 关于汇编,我也算是忘记的差不多了,根据你的结论,我仔细的比较了一下循环中的汇编代码,发现,基本操作都是一样的。仔细对比操作数的时候,我发现这两行的差别.....
已经测试过了。不是这两句的问题。

而且这两句都有间接寻址,所以应该不是间接寻址的问题

GFree_Wind2011-09-09 11:52:57

Rainyzzj: 关于汇编,我也算是忘记的差不多了,根据你的结论,我仔细的比较了一下循环中的汇编代码,发现,基本操作都是一样的。仔细对比操作数的时候,我发现这两行的差别.....
你挺仔细的。我昨晚也对比了汇编代码,也发现了这两句不同。其它的代码基本上效率应该一样——除去cache的因素。

但是昨天晚了,就没有验证是否是这个不同导致的效率不同。addl的l并不是指低位,而是长度后缀,表示long。

一会儿我会验证一下这两条语句的效率的。

Rainyzzj2011-09-09 01:18:22

关于汇编,我也算是忘记的差不多了,根据你的结论,我仔细的比较了一下循环中的汇编代码,发现,基本操作都是一样的。仔细对比操作数的时候,我发现这两行的差别。
0x080483b0 <+28>: addl $0x1,-0x8(%ebp)

0x080483ed <+44>: add -0xc(%ebp),%eax
我不清楚addl是不是表示低位相加的意思,姑且看做和add是一样的。
对比两个操作数,发现addl的第一个操作数($0x1)用的是立即数
而add的第一个操作数-0xc(%ebp),我查了一下,ebp应该是个基址指针,也就是说需要访问内存,而且既然是个基址指针,肯定还会有偏移量。也就是需要访问两次内存(应该是两次吧,记不清了,至少一次。)访问内存和立即数比起来,肯定慢多了。
另外,如果addl是低地址相加,位数上会比所有位相加,来的少,效率上也会高一点。这一点我只是猜测,不能确定。
我的个人理解。你看看我说的对不对。不对的地方请指正。