学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效。恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简单的函数strlen研究了一下,并对各种实现作了简单的效率测试。
strlen的函数原形如下:
size_t strlen(const char *str);
strlen返回str中字符的个数,其中str为一个以'\0'结尾的字符串(a null-terminated string)。
1. 简单实现
如果不管效率,最简单的实现只需要4行代码:

C++ Code
1
2
3
4
5
6
7
size_t strlen_a(const char *str)
{
    size_t length = 0 ;
    while (*str++ )
        ++ length;
    return  length;
}

也许可以稍加改进如下:

C++ Code
1
2
3
4
5
6
size_t strlen_b(const char *str)
{
    const char *cp =  str;
    while (*cp++ );
    return (cp - str - 1 );
}

2. 高效实现
很显然,标准库的实现肯定不会如此简单,上面的strlen_a以及strlen_b都是一次判断一个字符直到发现'\0'为止,这是非常低效的。比较高效的实现如下(在这里WORD表示计算机中的一个字,不是WORD类型):
(1) 一次判断一个字符直到内存对齐,如果在内存对齐之前就遇到'\0'则直接return,否则到(2);
(2) 一次读入并判断一个WORD,如果此WORD中没有为0的字节,则继续下一个WORD,否则到(3);
(3) 到这里则说明WORD中至少有一个字节为0,剩下的就是找出第一个为0的字节的位置然后return。
NOTE:
数据对齐(data alignment),是指数据所在的内存地址必须是该数据长度的整数倍,这样CPU的存取速度最快。比如在32位的计算机中,一个WORD为4 byte,则WORD数据的起始地址能被4整除的时候CPU的存取效率比较高。CPU的优化规则大概如下:对于n字节(n = 2,4,8...)的元素,它的首地址能被n整除才能获得最好的性能。
为了便于下面的讨论,这里假设所用的计算机为32位,即一个WORD为4个字节。下面给出在32位计算机上的C语言实现(假设unsigned long为4个字节):源码来着于glibc

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
typedef unsigned long  ulong;

size_t strlen_c(const char *str)
{

    const char *char_ptr;
    const ulong *longword_ptr;
    register ulong longword, magic_bits;

    for (char_ptr =  str; ((ulong)char_ptr
                           & (sizeof(ulong) - 1)) != 0 ;
            ++ char_ptr)
    {
        if (*char_ptr == '\0' )
            return char_ptr -  str;
    }

    longword_ptr = (ulong * )char_ptr;

    magic_bits = 0x7efefeffL ;

    while (1 )
    {

        longword = *longword_ptr++ ;

        if ((((longword + magic_bits) ^ ~longword) & ~magic_bits) != 0 )
        {

            const char *cp = (const char *)(longword_ptr - 1 );

            if (cp[0] == 0 )
                return cp -  str;
            if (cp[1] == 0 )
                return cp - str + 1 ;
            if (cp[2] == 0 )
                return cp - str + 2 ;
            if (cp[3] == 0 )
                return cp - str + 3 ;
        }
    }
}

3. 源码剖析
上面给出的C语言实现虽然不算特别复杂,但也值得花点时间来弄清楚,先看9-14行:

for (char_ptr = str; ((ulong)char_ptr & (sizeof(ulong) - 1)) != 0; ++char_ptr) {
if (*char_ptr == '\0')
return char_ptr - str;
}

上面的代码实现了数据对齐,如果在对齐之前就遇到'\0'则可以直接return char_ptr - str;
测试代码如下:

ASM Code
1
2
3
4
char szName[]=”Jack”;
char *p=szName;
p++;//p移动一个字节,本身的地址p是按照4个字节对齐的,移动后不再对齐
printf(“%d\n”,strlen(p));

第16行将longword_ptr指向数据对齐后的首地址longword_ptr = (ulong*)char_ptr;

第18行给magic_bits赋值(在后面会解释这个值的意义)

magic_bits = 0x7efefeffL;

第22行读入一个WORD到longword并将longword_ptr指向下一个WORD

longword = *longword_ptr++;

第24行的if语句是整个算法的核心,该语句判断22行读入的WORD中是否有为0的字节

if ((((longword + magic_bits) ^ ~longword) & ~magic_bits) != 0)

if语句中的计算可以分为如下3步:

(1) longword + magic_bits

其中magic_bits的二进制表示如下:

                  b3      b2       b1       b0

              31------------------------------->0

  magic_bits: 01111110 11111110 11111110 11111111

magic_bits中的31,24,16,8这些bits都为0,我们把这几个bits称为holes,注意在每个byte的左边都有一个hole。

检测0字节:

如果longword 中有一个字节的所有bit都为0,则进行加法后,从这个字节的右边的字节传递来的进位都会落到这个字节的最低位所在的hole上,而从这个字节的最高位则永远不会产生向左边字节的hole的进位。则这个字节左边的hole在进行加法后不会改变,由此可以检测出0字节;相反,如果longword中所有字节都不为0,则每个字节中至少有1位为1,进行加法后所有的hole都会被改变。

为了便于理解,请看下面的例子:

                  b3      b2       b1       b0

              31------------------------------->0

  longword:   XXXXXXXX XXXXXXXX 00000000 XXXXXXXX

+ magic_bits: 01111110 11111110 11111110 11111111

上面longword中的b1为0,X可能为0也可能为1。因为b1的所有bit都为0,而从b0传递过来的进位只可能是0或1,很显然b1永远也不会产生进位,所以加法后longword的第16 bit这个hole不会变。

(2)  ^ ~longword

这一步取出加法后longword中所有未改变的bit。

(3) & ~magic_bits

最后取出longword中未改变的hole,如果有任何hole未改变则说明longword中有为0的字节。

根据上面的描述,如果longword中有为0的字节,则if中的表达式结果为非0,否则为0。

NOTE:

如果b3为10000000,则进行加法后第31 bit这个hole不会变,这说明我们无法检测出b3为10000000的所有WORD。值得庆幸的是用于strlen的字符串都是ASCII标准字符,其值在0-127之间,这意味着每一个字节的第一个bit都为0。因此上面的算法是安全的。

一旦检测出longword中有为0的字节,后面的代码只需要找到第一个为0的字节并返回相应的长度就OK:

const char *cp = (const char*)(longword_ptr - 1); 
if (cp[0] == 0)
return
cp - str;
if (cp[1] == 0)
return cp - str + 1;
if (cp[2] == 0) return cp - str + 2;
if (cp[3] == 0)
return cp - str + 3;

4. 另一种实现

CPP Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

size_t strlen_d(const char * str)

{

    const char * char_ptr;

    const ulong * longword_ptr;

    register ulong longword, himagic, lomagic;

    for (char_ptr = str; ((ulong)char_ptr & (sizeof(ulong) - 1)) != 0; ++char_ptr)

    {

        if ( * char_ptr == '\0')

            return char_ptr - str;

    }

    longword_ptr = (ulong * )char_ptr;

    himagic = 0x80808080L;

    lomagic = 0x01010101L;

    while (1)

    {

        longword = * longword_ptr++;

        if (((longword - lomagic) & himagic) != 0)

        {

            const char * cp = (const char * )(longword_ptr - 1);

            if (cp[0] == 0)

                return cp - str;

            if (cp[1] == 0)

                return cp - str + 1;

            if (cp[2] == 0)

                return cp - str + 2;

            if (cp[3] == 0)

                return cp - str + 3;

        }

    }

}

上面的代码与strlen_c基本一样,不同的是:

magic_bits换成了himagic和lomagic

himagic = 0x80808080L;

lomagic = 0x01010101L;

以及 if语句变得比较简单了

if (((longword - lomagic) & himagic) != 0)

if语句中的计算可以分为如下2步:

(1) longword - lomagic

himagic和lomagic的二进制表示如下:

                b3      b2       b1       b0

            31------------------------------->0

  himagic:  10000000 10000000 10000000 10000000

  lomagic:  00000001 00000001 00000001 00000001

在这种方法中假设所有字符都是ASCII标准字符,其值在0-127之间,因此longword总是如下形式:

                b3      b2       b1       b0

            31------------------------------->0

  longword: 0XXXXXXX 0XXXXXXX 0XXXXXXX 0XXXXXXX

检测0字节:

如果longword 中有一个字节的所有bit都为0,则进行减法后,这个字节的最高位一定会从0变为1;相反,如果longword中所有字节都不为0,则每个字节中至少有1位为1,进行减法后这个字节的最高位依然为0。

(2)  & himagic

这一步取出每个字节最高位的1,如果有任意字节最高位为1则说明longword中有为0的字节。

根据上面的描述,如果longword中有为0的字节,则if中的表达式结果为非0,否则为0。

5. 汇编实现

VC CRT的汇编实现与前面strlen_c算法类似

ASM Code
1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

        page    ,132

        title   strlen - return the length of a null-terminated string

;***

;strlen.asm - contains strlen() routine

;

;       Copyright (c) Microsoft Corporation. All rights reserved.

;

;Purpose:

;       strlen returns the length of a null-terminated string,

;       not including the null byte itself.

;

;*******************************************************************************

        .xlist

        include cruntime.inc

        .list

page

;***

;strlen - return the length of a null-terminated string

;

;Purpose:

;       Finds the length in bytes of the given string, not including

;       the final null character.

;

;       Algorithm:

;       int strlen (const char * str)

;       {

;           int length = 0;

;

;           while( *str++ )

;                   ++length;

;

;           return( length );

;       }

;

;Entry:

;       const char * str - string whose length is to be computed

;

;Exit:

;       EAX = length of the string "str", exclusive of the final null byte

;

;Uses:

;       EAX, ECX, EDX

;

;Exceptions:

;

;*******************************************************************************

        CODESEG

        public  strlen

strlen  proc \

        buf:ptr byte

        OPTION PROLOGUE:NONE, EPILOGUE:NONE

        .FPO    ( 0, 1, 0, 0, 0, 0 )

string  equ     [esp + 4]

        mov     ecx,string              ; ecx -> string

        test    ecx,3                   ; test if string is aligned on 32 bits

        je      short main_loop

str_misaligned:

        ; simple byte loop until string is aligned

        mov     al,byte ptr [ecx]

        add     ecx,1

        test    al,al

        je      short byte_3

        test    ecx,3

        jne     short str_misaligned

        add     eax,dword ptr 0         ; 5 byte nop to align label below

        align   16                      ; should be redundant

main_loop:

        mov     eax,dword ptr [ecx]     ; read 4 bytes

        mov     edx,7efefeffh

        add     edx,eax

        xor     eax,-1

        xor     eax,edx

        add     ecx,4

        test    eax,81010100h

        je      short main_loop

        ; found zero byte in the loop

        mov     eax,[ecx - 4]

        test    al,al                   ; is it byte 0

        je      short byte_0

        test    ah,ah                   ; is it byte 1

        je      short byte_1

        test    eax,00ff0000h           ; is it byte 2

        je      short byte_2

        test    eax,0ff000000h          ; is it byte 3

        je      short byte_3

        jmp     short main_loop         ; taken if bits 24-30 are clear and bit

                                        ; 31 is set

byte_3:

        lea     eax,[ecx - 1]

        mov     ecx,string

        sub     eax,ecx

        ret

byte_2:

        lea     eax,[ecx - 2]

        mov     ecx,string

        sub     eax,ecx

        ret

byte_1:

        lea     eax,[ecx - 3]

        mov     ecx,string

        sub     eax,ecx

        ret

byte_0:

        lea     eax,[ecx - 4]

        mov     ecx,string

        sub     eax,ecx

        ret

strlen  endp

        end

     

6. 测试结果

为了对上述各种实现的效率有一个大概的认识,我在VC8和GCC下分别进行了测试,测试时均采用默认优化方式。下面是在GCC下运行几百万次后的结果(在VC8下的运行结果与此相似):

strlen_a

--------------------------------------------------

1:        515 ticks         0.515 seconds

       2:        375 ticks         0.375 seconds

       3:        375 ticks         0.375 seconds

       4:        375 ticks         0.375 seconds

       5:        375 ticks         0.375 seconds

   total:       2015 ticks         2.015 seconds

average:        403 ticks         0.403 seconds

--------------------------------------------------

strlen_b

--------------------------------------------------

1:        360 ticks          0.36 seconds

       2:        390 ticks          0.39 seconds

       3:        375 ticks         0.375 seconds

       4:        360 ticks          0.36 seconds

       5:        375 ticks         0.375 seconds

   total:       1860 ticks          1.86 seconds

average:        372 ticks         0.372 seconds

--------------------------------------------------

strlen_c

--------------------------------------------------

1:        187 ticks         0.187 seconds

       2:        172 ticks         0.172 seconds

       3:        187 ticks         0.187 seconds

       4:        187 ticks         0.187 seconds

       5:        188 ticks         0.188 seconds

   total:        921 ticks         0.921 seconds

average:        184 ticks        0.1842 seconds

--------------------------------------------------

strlen_d

--------------------------------------------------

1:        172 ticks         0.172 seconds

       2:        187 ticks         0.187 seconds

       3:        172 ticks         0.172 seconds

       4:        187 ticks         0.187 seconds

       5:        188 ticks         0.188 seconds

   total:        906 ticks         0.906 seconds

average:        181 ticks        0.1812 seconds

--------------------------------------------------

strlen

--------------------------------------------------

1:        187 ticks         0.187 seconds

       2:        172 ticks         0.172 seconds

       3:        188 ticks         0.188 seconds

       4:        172 ticks         0.172 seconds

       5:        187 ticks         0.187 seconds

   total:        906 ticks         0.906 seconds

average:        181 ticks        0.1812 seconds

--------------------------------------------------

strlen源码剖析的更多相关文章

  1. strlen源码剖析(可查看glibc和VC的CRT源代码)

    学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效.恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简单的 ...

  2. 【转】strlen源码

    strlen源码剖析 学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效.恰好手中就有glibc和VC的CRT源代码, ...

  3. 菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t[转]

    菜鸟nginx源码剖析数据结构篇(三) 单向链表 ngx_list_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csd ...

  4. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  5. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  6. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  7. 基于mybatis-generator-core 1.3.5项目的修订版以及源码剖析

    项目简单说明 mybatis-generator,是根据数据库表.字段反向生成实体类等代码文件.我在国庆时候,没事剖析了mybatis-generator-core源码,写了相当详细的中文注释,可以去 ...

  8. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  9. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

随机推荐

  1. Android TextView drawableLeft 在代码中实现

    方法1 Drawable drawable= getResources().getDrawable(R.drawable.drawable); /// 这一步必须要做,否则不会显示. drawable ...

  2. 防止自己的网站被别人frame引用造成钓鱼

    自己负责的某一网站,最近被不法份子通过<frame>的方式引入,用户点击对方的域名后,看到的内容跟自己网站一模一样.但是右击查看源码就会发现其中的原理: <!DOCTYPE HTML ...

  3. 电脑技巧---完全控制面板---上帝模式(God Mode)

    简介 上帝模式,即"God Mode”,或称为“完全控制面板”.是Windows 系统中隐藏的一个简单的文件夹窗口,但包含了几乎所有Windows系统的设置,如控制面板的功能.界面个性化.辅 ...

  4. Android应用如何开机自启动、自启动失败原因

    本文主要介绍Android应用如何开机自启动.自启动失败的原因.adb命令发送BOOT_COMPLETED.问题:应用程序是否可以在安装后自启动,没有ui的纯service应用如何启动?答案马上揭晓^ ...

  5. MySql级联操作

    转自:http://blog.csdn.net/codeforme/article/details/5539454 外键约束对子表的含义:       如果在父表中找不到候选键,则不允许在子表上进行i ...

  6. javascript小练习—记住密码提示框

    px/px solid redpxpx]; var oTips = document.getElementById("tips"); oP.onmousemove = functi ...

  7. jquery中的uploadfile关于图片上上传的插件的应用

    ajaxFileUpload是一个异步上传文件的jQuery插件. 传一个不知道什么版本的上来,以后不用到处找了. 语法:$.ajaxFileUpload([options]) options参数说明 ...

  8. MySQL Select 优化

    准备: create table t(x int primary key,y int unique,z int); insert into t(x,y,z) values(1,1,1),(2,2,2) ...

  9. NAND FLASH特性说明

    1.NAND FLASH 的特殊性. 1)存在坏块.由于NAND生产工艺的原因,出厂芯片中会随机出现坏块.坏块在出厂时已经被初始化,并在特殊区域中标记为不可用,在使用过程中如果出现坏块,也需要进行标记 ...

  10. 所有的GUI Toolkit,类型之多真开眼界

    The GUI Toolkit, Framework Page User interfaces occupy an important part of software development. Th ...