如何尽快实现strlen

时间:2010-03-03 15:05:04

标签: c++ algorithm

假设您正在使用x86 32位系统。你的任务是尽快实现strlen。

你需要注意两个问题: 地址对齐。 2.读取机器字长(4字节)的存储器。

在给定的字符串中找到第一个对齐地址并不难。

然后我们可以用4个字节读取一次内存,并计算总长度。但是,一旦在4个字节中有一个零字节,我们应该停止,并在零字节之前计算左字节。为了快速检查零字节,有一个来自glibc的代码片段:

unsigned long int longword, himagic, lomagic;
himagic = 0x80808080L;  
lomagic = 0x01010101L;

// There's zero byte in 4 bytes.
if (((longword - lomagic) & ~longword & himagic) != 0) {
    // do left thing...
}

我在Visual C ++中使用它,与CRT的实现进行比较。 CRT比上面的快得多。

我不熟悉CRT的实现,他们是否使用更快的方法来检查零字节?

7 个答案:

答案 0 :(得分:8)

您可以在创建字符串时将字符串的长度与字符串一起保存,就像在Pascal中一样。

答案 1 :(得分:7)

第一个CRT的第一个是汇编程序中的直接。你可以在这里看到它的源代码C:\Program Files\Microsoft Visual Studio 9.0\VC\crt\src\intel\strlen.asm(这是针对VS 2008)

答案 2 :(得分:4)

这取决于。微软的库真的有两个不同版本的strlen。一个是C语言中的可移植版本,它是关于strlen最简单的版本,非常接近(可能等同于):

size_t strlen(char const *str) { 
    for (char const *pos=str; *pos; ++pos)
        ;
    return pos-str;
}

另一种是汇编语言(仅用于Intel x86),与上面的内容非常相似,至少就加载4个字节而言,检查其中一个是零,并做出适当的反应。唯一明显的区别是,它们不是减去,而是基本上添加预先否定字节并添加。即而不是word-0x0101010101,他们使用word + 0x7efefeff

答案 3 :(得分:2)

还有使用REPNE SCAS指令对的编译器内部版本,虽然它们通常在较旧的编译器上,但它们仍然可以非常快。还有SSE2版本的strlen,例如Dr Agner Fog's performance library's implementation,或类似this

答案 4 :(得分:1)

删除那些' L'后缀并看到... 您正在推广所有计算到#34; long"!在我的32位测试中,仅此一项费用就会增加一倍。

我还做了两次微观优化:

  • 由于我们使用扫描的大多数字符串都包含0到127范围内的ASCII字符,高位(几乎)从不设置,所以只在第二次测试中检查它。

  • 增加索引而不是指针,这在某些体系结构(尤其是x86)上更便宜,并为您提供免费' ...


uint32_t gatopeich_strlen32(const char* str)
{
    uint32_t *u32 = (uint32_t*)str, u, abcd, i=0;
    while(1)
    {
        u = u32[i++];
        abcd = (u-0x01010101) & 0x80808080;
        if (abcd && // If abcd is not 0, we have NUL or a non-ASCII char > 127...
             (abcd &= ~u)) // ... Discard non-ASCII chars
        {
        #if BYTE_ORDER == BIG_ENDIAN
            return 4*i - (abcd&0xffff0000 ? (abcd&0xff000000?4:3) : abcd&0xff00?2:1);
        #else
            return 4*i - (abcd&0xffff ? (abcd&0xff?4:3) : abcd&0xff0000?2:1);
        #endif
        }
    }
}

答案 5 :(得分:0)

假设您知道最大可能的长度,并且在使用之前已经将内存启动到\ 0,您可以进行二进制拆分并根据值进行左/右(\ 0,左侧拆分,否则拆分)对)。这样你就可以大大减少查找长度所需的支票数量。不是最佳的(需要一些设置),但应该非常快。

// Eric

答案 6 :(得分:0)

显然,在汇编程序中制作这样的紧密循环会是最快的,但是如果你想要/需要在C(++)中保持它更易读和/或可移植,你仍然可以提高标准的速度使用register keyword进行功能。

register关键字提示编译器将计数器存储在CPU上的寄存器中而不是存储器中,这将显着加快循环速度。

但请注意,register关键字只是一个建议,如果编译器认为它可以做得更好,编译器可以自由忽略它,特别是如果使用了某些优化选项。也就是说,虽然对于三重for循环中的本地类变量几乎肯定会被忽略,但它可能会对下面的代码感到荣幸,从而提高了性能(几乎与汇编程序版本相当) ):

size_t strlen ( const char* s ) {
  for (register const char* i=s; *i; ++i);
  return (i-s);
}