【问题标题】:How to write a better strlen function?如何编写更好的strlen函数?
【发布时间】:2011-09-28 21:15:42
【问题描述】:

我正在阅读“Write Great Code Volume 2”,它显示了以下 strlen 实施:

int myStrlen( char *s )
{
    char *start;
    start = s;
    while( *s != 0 )
    {
        ++s;
    }
    return s - start;
}

这本书说这种实现对于没有经验的 C 程序员来说是典型的。在过去的 11 年里,我一直在用 C 编写代码,但我看不出如何在 C 中编写比这更好的函数(我可以想到在汇编中编写更好的东西)。怎么可能用 C 写出比这更好的代码呢?我查看了 glibc 中 strlen 函数的标准库实现,但我无法理解其中的大部分内容。在哪里可以找到有关如何编写高度优化的代码的更好信息?

【问题讨论】:

  • 您确定这是优化问题吗?还是只是标准的安全问题?
  • @Victor 不要相信你读到的一切。该功能很快足够
  • 我曾经在一个 i386 系统的汇编程序中编写了strlen(),该系统使用 CPU 字符串 (REP) 操作码,运行速度比优化的 C 代码快 6 倍。
  • @Loadmaster:你能把这段代码贴出来吗?
  • 我反对 ptrdiff_tint 的演员阵容,是的,你可能没有将 2GB 字符串传递给 strlen(),但它仍然很草率。此外,编译器可能会从int i=0; while(s[i]) i++; return i; 生成更好的代码,因为它可以更多地了解您正在使用指针做什么(即它可以更好地分析该循环)。

标签: c string optimization pointers c-strings


【解决方案1】:

来自Optimising strlen(),Colm MacCarthaigh 的博文:

不幸的是,在 C 中,我们注定要实现 O(n),最好的情况,但我们还没有完成……我们可以对 n 的大小做一些事情。

它提供了一个很好的例子,说明您可以在哪些方向上加快速度。以及它的另一个引述

有时速度真的很快会让你真的很疯狂。

【讨论】:

    【解决方案2】:

    维克多,看看这个:
    http://en.wikipedia.org/wiki/Strlen#Implementation

    附:你不理解glibc版本的原因可能是因为它使用位移来查找\0。

    【讨论】:

    • 我的猜测是,使用适度的编译器,这将产生与 OPs 实现完全相同相同的字节码...
    • @Martin:你不能检查一个“单词”与零,这是行不通的
    • @Victor:对不起,我不是故意要诋毁你的编程能力。如果您正在寻找 glibc 实现的解释,请发布一个问题,我相信比我更聪明的人能够解释它。
    • 没问题,我没有被冒犯,我只是在评论。
    • 提到的特殊情况 x86 指令实际上比现代 CPU IIRC 上的循环慢
    【解决方案3】:

    对于初学者来说,这对于像 UTF-8 这样的编码毫无价值...也就是说,计算 UTF-8 字符串中的字符数更复杂,而字节数当然同样容易计算计算,比如说,一个 ASCII 字符串。

    通常,您可以通过读入更大的寄存器在某些平台上进行优化。由于到目前为止发布的其他链接没有这样的示例,这里有一些低端的伪伪代码:

    int size = 0;
    int x;
    int *caststring = (int *) yourstring;
    while (int x = *caststring++) {
      if (!(x & 0xff)) /* first byte in this int-sized package is 0 */ return size;
      else if (!(x & 0xff00)) /* second byte etc. */ return size+1;
      /* rinse and repeat depending on target architecture, i.e. twice more for 32 bit */
      size += sizeof (int);
    }
    

    【讨论】:

    • 这可能不会提高性能,我感觉它只会让情况变得更糟。
    • @yi_H:缺点:每个字节有一个额外的 AND。优点:内存负载减少 75%,跳转次数减少 75%。哪一方赢得比赛几乎可以肯定是特定于架构的。我不知道它将如何在哪些架构上执行,所以你很可能是对的。但你也可能是错的。 ;)
    • 缓存行的负载实际上减少了 75%,因为这些是连续字节。
    • 是的。完全正确。也许我应该重新考虑保持清醒的事情。
    【解决方案4】:

    正如其他人指出的那样,更快的算法读取整个单词而不是单个字符,并使用bitwise operations 来查找终止空值。如果您采用这种方法,请注意字对齐指针,因为某些 CPU 架构不允许您从未对齐的地址读取字(即使在不需要对齐的架构上,这也是触发段错误的好方法)。

    底线:

    伟大的代码强调可读性而不是速度,除了对性能最关键的情况。尽可能清楚地编写代码,并且只优化被证明是瓶颈的部分。

    【讨论】:

    • 我猜想“优秀的代码是对读者友好的代码”这一论点在以性能为目标的 C 标准库的情况下不成立。
    • 由于标准库被如此广泛和频繁地使用,“性能关键”异常是合适的。尽管如此,他们中的大多数人都可以使用更好的文档......
    【解决方案5】:

    读取与机器数据总线大小不同的变量是昂贵的,因为机器只能读取该大小的变量。因此,无论何时请求不同大小(比方说更小)的东西,机器必须做一些工作以使其看起来像请求大小的变量(如移位位)。 因此,您最好以机器大小的字读取数据,然后使用 AND 操作检查是否为 0。此外,在扫描字符串时,请确保从对齐的起始地址开始。

    【讨论】:

      【解决方案6】:

      回答 OP 关于在哪里可以找到如何编写代码以提高性能的建议的问题,这里是 link 到 MIT OpenCourse 关于编写优化的 C 代码(查找页面左侧的“材料”链接)。

      【讨论】:

        【解决方案7】:

        以下应该比朴素算法更快,并且适用于 32/64 位。

        union intptr {
            char* c;
            long* l;
        #define LSIZE sizeof(long)
        };
        
        #define aligned_(x, a) \
            ((unsigned long) (x) % (a) == 0)
        
        #define punpktt_(x, from, to) \
            ((to) (-1)/(from) (-1)*(from) (x))
        #define punpkbl_(x) \
            punpktt_(x, unsigned char, unsigned long)
        
        #define plessbl_(x, y) \
            (((x) - punpkbl_(y)) & ~(x) & punpkbl_(0x80))
        #define pzerobl_(x) \
            plessbl_(x, 1)
        
        static inline unsigned long maskffs_(unsigned long x)
        {
            unsigned long acc = 0x00010203UL;
            if (LSIZE == 8)
               acc = ((acc << 16) << 16) | 0x04050607UL;
            return ((x & -x) >> 7) * acc >> (LSIZE*8-8);
        }
        
        size_t strlen(const char* base)
        {
            union intptr p = { (char*) base };
            unsigned long mask;
        
            for ( ; !aligned_(p.c, LSIZE); p.c++ )
                if (*p.c == 0)
                    return p.c - base;
        
            while ( !(mask = pzerobl_(*p.l)) )
                p.l++;
            return p.c - base + maskffs_(mask);
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-07-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多