【问题标题】:Fast way to find an ascii character in long integers using bitwise operations?使用按位运算在长整数中查找 ascii 字符的快速方法?
【发布时间】:2013-12-15 21:35:55
【问题描述】:

我正在执行 strchr 并尝试优化我的代码以使用我机器的 64 位属性。因此,我将字符串转换为长整数,从而一次比较 8 个字符。

目前,我有:

int    has_char(unsigned long word, unsigned long c)
{
    if((word & 0xFF00000000000000) == (c << 56) ) return (1);
    if((word & 0x00FF000000000000) == (c << 48))  return (1);
    if((word & 0x0000FF0000000000) == (c << 40))  return (1);
    if((word & 0x000000FF00000000) == (c << 32))  return (1);
    if((word & 0x00000000FF000000) == (c << 24))  return (1);
    if((word & 0x0000000000FF0000) == (c << 16))  return (1);
    if((word & 0x000000000000FF00) == (c << 8))   return (1);
    if((word & 0x00000000000000FF) == c)          return (1);
    return (0); /* Not found, returning 0 */
}

char    strchr(const char *s, int c)
{
    const char      *curr;
    const long      *str;
    unsigned long    wd;

    str = (long *)s;
    while (1) {
        wd = *str;
        if (has_char(wd, (unsigned long)c)) {
            curr = (char *)str;
                while (*curr) {
                    if (*curr == (char)c)
                        return ((char *)curr);
                    curr++;
                }
        }
        if ((wd - 0x0101010101010101)
            & ~wd & 0x8080808080808080) /* End of string and character not found, exit */
            return (NULL);
    str++;
    }
}

效果很好,但我的 has_char 效率很低,它测试了 8 次字符值。有没有办法做一个独特的测试(掩码?)如果字符出现在单词中则返回 1,如果不存在则返回 0?

感谢您的帮助!

【问题讨论】:

  • 您已经在循环结束时进行了每字节空测试。只需从 c
  • 看SSE指令集
  • 如果字符串长度不是 8 的倍数,您的代码将无法正常工作。在某些平台上,long 必须在 8 字节边界上对齐,因此您不能转换任何 @ 987654323@ 到unsigned long *
  • 嗯,如果我超出 8 字节边界会发生什么?分段故障 ?我的电脑没有抱怨……嗯,还没有?

标签: c micro-optimization


【解决方案1】:

很好,这是所要求的准确代码:

// Return a non-zero mask if any of the bytes are zero/null, as per your original code
inline uint64_t any_zeroes(uint64_t value) {
    return (value - 0x0101010101010101) & ~value & 0x8080808080808080;
}

char *strchr(const char *s, int ch) {
    // Pre-generate a 64-bit comparison mask with the character at every byte position
    uint64_t mask = (unsigned char) ch * 0x0101010101010101;
    // Access the string 64-bits at a time.
    // Beware of alignment requirements on most platforms.
    const uint64_t *word_ptr = (const uint64_t *) s;

    // Search through the string in 8-byte chunks looking for either any character matches
    // or any null bytes
    uint64_t value;
    do {
        value = *word_ptr++:
        // The exclusive-or value ^ mask will give us 0 in any byte field matching the char
        value = any_zeroes(value) | any_zeroes(value ^ mask);
    } while(!value);

    // Wind-down and locate the final character. This may be done faster by looking at the
    // previously generated zero masks but doing so requires us to know the byte-order
    s = (const char *) --word_ptr;
    do {
        if(*s == (char) ch)
            return (char *) s;
    } while(*s++);
    return NULL;
}

当心:写在我的头上。

【讨论】:

  • 非常感谢代码示例和详细的 cmets !
  • 请注意,此实现可能会读取超出字符串结尾和it has been noted this is technically UB。在 64 位环境中它可能会或可能不会“更糟”。
【解决方案2】:

首先,新建一个变量c8,每个位置都是一个c。

unsigned long c8= (c << 56L) | ( c << 48L ) | ... | ( c << 8 ) | c ;

在循环外执行一次,这样您就不会重新计算。

然后将c8word 异或,并测试每个字节是否为零。要并行执行此操作,有几种选择:

如果你想变得丑陋,我们可以开始做一些平行折叠。首先让我们为每个字节将所有的折叠到一个位置:

unsigned long ltmp ;
ltmp= word | (0xf0f0f0f0f0f0f0f0 & ( word >> 4 )) ;
ltmp &= 0x0f0f0f0f0f0f0f0f ;
ltmp |= ( ltmp >> 2 ) ;
ltmp |= ( ltmp >> 1 ) ;
ltmp &= 0x0101010101010101 ;

return ( ltmp != 0x0101010101010101 ) ;

或者,cmets是下面的测试:

((wd - 0x0101010101010101) & ~wd & 0x8080808080808080)

相当于前面所有的操作。

顺便说一句,形式:if (a) return 1 ; return 0 ; 可以写成return a ;return a != 0 ;

【讨论】:

  • 所以无论如何,如果我的字符出现在 8 个位置中的任何一个,我都必须测试我的 word 8 次?
  • @achedeuzot:不,您已经编写了并行零测试。使用它。
  • 使用0x0101010101010101 * c 是一种更快的方法
  • 对不起,我对此很陌生,所以有人可以告诉我代码或伪代码应该是什么样子。或者是否有一个很好的按位在线计算器(我找不到,通常它只给出没有操作的结果)可以告诉我每一步都发生了什么。
  • 测试if ((wd - 0x0101010101010101 &amp; ~wd &amp; 0x8080808080808080),已经在OP的代码中用于测试终止零,也可以在这里使用。
猜你喜欢
  • 2012-02-11
  • 1970-01-01
  • 1970-01-01
  • 2013-10-28
  • 1970-01-01
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多