2016-01-06 12 views
11

glibcののstrlenの実装に関する2つの質問があります。strlenの実装におけるコードの理解

  1. 実装では、「穴」付きのマジックナンバーが使用されます。私はこの仕組みが理解できません。誰かがこのスニペットを理解できるように助けてください:

    size_t 
    strlen (const char *str) 
    { 
        const char *char_ptr; 
        const unsigned long int *longword_ptr; 
        unsigned long int longword, himagic, lomagic; 
    
        /* Handle the first few characters by reading one character at a time. 
         Do this until CHAR_PTR is aligned on a longword boundary. */ 
        for (char_ptr = str; ((unsigned long int) char_ptr 
          & (sizeof (longword) - 1)) != 0; 
         ++char_ptr) 
        if (*char_ptr == '\0') 
         return char_ptr - str; 
    
        /* All these elucidatory comments refer to 4-byte longwords, 
         but the theory applies equally well to 8-byte longwords. */ 
    
        longword_ptr = (unsigned long int *) char_ptr; 
    
        /* Bits 31, 24, 16, and 8 of this number are zero. Call these bits 
         the "holes." Note that there is a hole just to the left of 
         each byte, with an extra at the end: 
    
         bits: 01111110 11111110 11111110 11111111 
         bytes: AAAAAAAA BBBBBBBB CCCCCCCC DDDDDDDD 
    
         The 1-bits make sure that carries propagate to the next 0-bit. 
         The 0-bits provide holes for carries to fall into. */ 
    
        himagic = 0x80808080L; 
         lomagic = 0x01010101L; 
         if (sizeof (longword) > 4) 
         { 
          /* 64-bit version of the magic. */ 
          /* Do the shift in two steps to avoid a warning if long has 32 bits. */ 
          himagic = ((himagic << 16) << 16) | himagic; 
          lomagic = ((lomagic << 16) << 16) | lomagic; 
         } 
         if (sizeof (longword) > 8) 
         abort(); 
    
         /* Instead of the traditional loop which tests each character, 
          we will test a longword at a time. The tricky part is testing 
          if *any of the four* bytes in the longword in question are zero. */ 
         for (;;) 
         { 
          longword = *longword_ptr++; 
    
          if (((longword - lomagic) & ~longword & himagic) != 0) 
         { 
          /* Which of the bytes was the zero? If none of them were, it was 
           a misfire; continue the search. */ 
    
          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; 
          if (sizeof (longword) > 4) 
          { 
           if (cp[4] == 0) 
          return cp - str + 4; 
           if (cp[5] == 0) 
          return cp - str + 5; 
           if (cp[6] == 0) 
          return cp - str + 6; 
        if (cp[7] == 0) 
         return cp - str + 7; 
    }}} 
    

    どのような魔法の番号が使用されていますか?

  2. NULL文字と戻りカウントまで単にポインタをインクリメントしないのはなぜですか?このアプローチはより速いのですか?それはなぜそうですか?

+1

ほとんどのアーキテクチャでは、glibcはさらに高速な機能を使用します。たとえば、最新のIntelチップでは、SIMD拡張を使用してチェックをベクトル化します。 – rici

答えて

12

これに代えて個々のバイトをチェック、それらの一方がゼロ(文字列の末尾)であるかどうかを確認するために、一度に4バイト(32ビット)、あるいは8(64ビット)を見るために使用され。

unsigned int v; // 32-bit word to check if any 8-bit byte in it is 0 
bool hasZeroByte = ~((((v & 0x7F7F7F7F) + 0x7F7F7F7F) | v) | 0x7F7F7F7F); 

いくつかは、より多くのBit Twiddling Hacksを参照してください:ここでは

はNULLバイトをチェックするための一例です。

ここで使用されるもの(32ビットの例):

速い方法まだあり - 以下 定義されている使用hasless(V、1)。それは4回の操作で動作し、 の部分集合を必要としません。これは

#define haszero(v) (((v) - 0x01010101UL) & ~(v) & 0x80808080UL)

に簡素化部分式(V - 0x01010101UL)、Vの対応するバイトが は0x80よりゼロ以上であるときはいつでも に設定された高ビットに任意のバイトを評価します。サブ式〜v & 0x80808080ULは、 を上位ビットに設定します。ここで、vのバイトには上位ビットが設定されていません( バイトは0x80未満です)。最後に、 の2つのサブ式を論理積することによって、 の最初の サブ式の0x80より大きい値によって設定される上位ビットが、秒。

一度に1バイトを調べることは、完全な整数値(レジスタ幅)を調べることと同じくらい多くのCPUサイクルを要します。このアルゴリズムでは、完全な整数がチェックされ、ゼロが含まれているかどうかが確認されます。そうでない場合、ほとんど指示が使用されず、ジャンプを次の完全整数にすることができます。内部に0バイトがある場合、さらに正確な位置を確認するためのチェックが行われます。

+1

さらに、gcc 'strlen'の実装は、8バイトの整数がサポートされているアーキテクチャを利用するために最適化されています。あなたの上には、一度に4バイトのヌルを探すことに限られています。 'strlen'の' if(sizeof(longword)> 4) '比較は、追加の4バイトの比較を拡張します。いずれの方法でも、約32文字を超える文字列の 'strlen'パフォーマンスが向上します。 (char * by charチェック*で得られるものの上)。いい答えだ。 –

+0

@greybeard - 更新済み - ありがとうございます。 –

関連する問題