C++ 如何尽快实现strlen

C++ 如何尽快实现strlen,c++,algorithm,C++,Algorithm,假设您使用的是x86 32位系统。您的任务是尽快实现strlen 有两个问题需要注意: 1.地址对齐。 2.以机器字长度(4字节)读取内存 在给定的字符串中不难找到第一个对齐地址 然后,我们可以用4个字节读取内存一次,并将其计算为总长度。但我们应该在4个字节中有一个零字节时停止,并在零字节之前计算剩余字节。为了快速检查零字节,glibc提供了一段代码片段: unsigned long int longword, himagic, lomagic; himagic = 0x80808080L;

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

有两个问题需要注意: 1.地址对齐。 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的实现,他们是否使用了更快的方法来检查零字节?

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

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

这取决于具体情况。微软的图书馆实际上有两个不同版本的strlen。一个是C语言的可移植版本,它是strlen最简单的版本,非常接近(可能相当于):


另一个是汇编语言(仅用于英特尔x86),与上面的内容非常类似,至少加载4个字节,其中一个字节的检查为零,并做出适当的反应。唯一明显的区别是,它们基本上不是减法,而是先对字节求反再加。也就是说,它们使用
word+0x7efefeff
而不是
word-0x0101010101

假设您知道最大可能长度,并且在使用前已将内存初始化为\0,则可以执行二进制拆分,并根据值向左/向右移动(\0,向左拆分,否则向右拆分)。这样可以大大减少查找长度所需的检查数量。不是最优的(需要一些设置),但应该非常快


//Eric

也有使用REPNE SCAS指令对的编译器内部版本,尽管这些版本通常在较旧的编译器上,但它们仍然可以非常快。还有SSE2版本的strlen,例如,或者类似的东西,显然,在assembler中构建这样一个紧密的循环是最快的,但是如果您想/需要在C(++)中保持它更人性化的可读性和/或可移植性,您仍然可以通过使用来提高标准函数的速度

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

但是,请注意,
register
关键字只是一个建议,如果编译器认为它可以做得更好,则可以随意忽略它,尤其是在使用某些优化选项的情况下。也就是说,对于一个三重for循环中的局部类变量来说,它几乎肯定会被忽略,它很可能会被下面的代码所尊敬,从而改进性能(与汇编版本几乎一致):


删除那些“L”后缀,然后查看。。。您正在将所有计算提升为“长”!在我的32位测试中,仅此一项成本就翻了一番

我还进行了两项微观优化:

  • 由于我们使用扫描的大多数字符串都由0~127范围内的ASCII字符组成,高位(几乎)从未设置过,因此只需在第二次测试中检查它

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



你有关于差异的数字吗?编译上述代码时是否启用了完全优化?是否有原因不能只使用各自编译器/库附带的strlen?图书馆的作者可能已经花了一些时间来获得所有可能的优化。同样,在回答中,由于这是标记C++,你能使用应该存储长度的STD::string吗?@马克,它可能是一个家庭作业,因此“你的任务是实现斯特伦”。虽然性能似乎不是问题的一部分,但OP试图弄清楚他们提出的实现为何如此缓慢。您是否在启用优化并以发布模式编译代码?也请检查这个。那些“L”后缀使编译器将所有操作升级到64位!删除它们并查看下面我的完整答案[这意味着每次修改字符串内容时,都会将NUL字符追加到缓冲区的末尾。+1我不确定这是否解决了这个问题,但是,说真的,以null结尾的字符串是C语言最大的弱点之一,并且会导致使用幼稚代码时出现荒谬的大O性能。最好避免这种情况没错。你只应该计算一次StrLin,然后缓存这个结果。在C++中使用字符串类的优势。当然,正如你所说的,这对你的问题没有帮助。而且,和其他很多东西一样,C++的弱点已经根深蒂固地融入到其他语言和操作系统中。infinitum@BlueRaja当前位置
C
C++
std::string
,它通常有一个固定的时间
size
成员函数…尽管标准不要求它。@Matthieu:
“abcd”
有type
char[]
,而不是
std::string
,这导致该漏洞仍然普遍存在。我能在我的机器上找到没有安装MS-VS的汇编语言中的Microsoft strlen实现的源代码吗?我不在MS windows上,但我很想试着理解你最后的评论,并检查它的速度有多快。@Jack:我想你可以看看在他们的网站上,但我不确定它是否在那里。
size_t strlen(char const *str) { 
    for (char const *pos=str; *pos; ++pos)
        ;
    return pos-str;
}
size_t strlen ( const char* s ) {
  for (register const char* i=s; *i; ++i);
  return (i-s);
}
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
        }
    }
}