zoukankan      html  css  js  c++  java
  • strlen源码剖析

     

    学习高效编程的有效途径之一就是阅读高手写的源代码,CRT(C/C++ Runtime Library)作为底层的函数库,实现必然高效。恰好手中就有glibc和VC的CRT源代码,于是挑了一个相对简单的函数strlen研究了一下,并对各种实现作了简单的效率测试。
    strlen的函数原形如下:
    size_t strlen(const char *str);
    strlen返回str中字符的个数,其中str为一个以''结尾的字符串(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都是一次判断一个字符直到发现''为止,这是非常低效的。比较高效的实现如下(在这里WORD表示计算机中的一个字,不是WORD类型):
    (1) 一次判断一个字符直到内存对齐,如果在内存对齐之前就遇到''则直接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 == '' )
               
    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 == '')
    return char_ptr - str;
    }

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

    ASM Code
    1
    2
    3
    4
    char szName[]=”Jack”;
    char *p=szName;
    p++;//p移动一个字节,本身的地址p是按照4个字节对齐的,移动后不再对齐
    printf(“%d ”,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 == '')
               
    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
    --------------------------------------------------

  • 相关阅读:
    Java synchronized对象级别与类级别的同步锁
    java并发编程JUC第十二篇:AtomicInteger原子整型
    java并发编程JUC第十一篇:如何在线程之间进行对等数据交换
    java并发编程JUC第十篇:CyclicBarrier线程同步
    java并发编程JUC第九篇:CountDownLatch线程同步
    java并发编程工具类JUC第八篇:ConcurrentHashMap
    分享Sql性能优化的一些建议
    java并发编程工具类JUC第七篇:BlockingDeque双端阻塞队列
    java并发编程工具类JUC第六篇:SynchronousQueue同步队列
    java并发编程工具类JUC第五篇:PriorityBlockingQueue优先级队列
  • 原文地址:https://www.cnblogs.com/pianist/p/3322020.html
Copyright © 2011-2022 走看看