shufti匹配

hyperscan的shufti匹配,用于单字符集的匹配,核心使用了intel的_mm_shuffle_epi8指令,其为针对16字节变量的指令。如下函数pshufb_m128,根据第二个参数b中保存的索引值,由参数a中获得相应位置的值,保存到result中。参数b中每个字节的低四位作为索引值,索引值的范围为:[0, 15],对应参数b中的16个字节,查询不会超出范围。

另外,如果参数b中某个字节的最高位为1,不执行查询操作,结果固定为0。

static really_inline
m128 pshufb_m128(m128 a, m128 b) {
    m128 result;
    result = _mm_shuffle_epi8(a, b);
    return result;
}

如下示例,参数b中的第4、7、12字节的索引值2、9、e对应的结果为c、h、a。参数b中的第10个字节值为8a,最高位为1,结果值为0。

offset  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
a       9  4  c  h  2a n  s  x  a  h  3  7  1a 3c 9  m   (byte)
               \                  /              /
                \                /              /
                 \              /              /
                  \            /              /
b       .  .  .  .  2  .  .  9  .  .  8a .  e  .  .  .   (byte)
                    |        |        |     |
result  .  .  .  .  c  .  .  h  .  .  0  .  a  .  .  .   (byte)

shufti匹配

函数shuftiExec如下,参数mask_lo和mask_hi向量分别对应输入数据字节的低四位和高四位要查询的目的表,两次查询结果相与不为0,表明发送匹配。例如,对于匹配项:[/dabc],hyperscan在编译器构建如下的表。mask_hi每个字节表示一行,mask_lo表示列中所有非空行的并集。

offset   0    1   2    3    4    5   6   7    8   9  ...  f
         x8  x18 x18  x18  x18  x8  x8  x8  x8  x8           (mask_lo)               

0   x1
1   x2
2   x4
3   x8    0  1    2    3    4   5   6   7    8   9
4   x10      a    b    c 
5   x20
6   x40
7   x80
8   x0
.   .
  (mask_hi)

对于输入字符为ASCII字符1时,其十六进制值为0x31,高四位值为索引3在mask_hi中的值为0x8;低四位值为索引1,在mask_lo中的值为0x18。结果值0x8与0x18相与等于0x8,即ASCII字符1查表结果不等于零,与表达式[/dabc]产生了匹配。

对于输入字符为ASCII字符a时,其十六进制值为0x41,在mask_hi中查询结果值为0x10,在mask_lo中查询结果值为0x18,两者相与为0x10,不等于零,与表达式[/dabc]产生了匹配。

首先,如果输入数据的长度不足16字节,由函数shuftiFwdSlow处理。

const u8 *shuftiExec(m128 mask_lo, m128 mask_hi, const u8 *buf, const u8 *buf_end) {
    assert(buf && buf_end);
    assert(buf < buf_end);
                    
    // Slow path for small cases.
    if (buf_end - buf < 16) {
        return shuftiFwdSlow((const u8 *)&mask_lo, (const u8 *)&mask_hi, buf, buf_end);
    }

其次,对buf开始位置的16个字节执行匹配检测,发生匹配返回匹配位置。

    const m128 zeroes = zeroes128();
    const m128 low4bits = _mm_set1_epi8(0xf);
    const u8 *rv;
       
    size_t min = (size_t)buf % 16;
    assert(buf_end - buf >= 16);
   
    // Preconditioning: most of the time our buffer won't be aligned.
    m128 chars = loadu128(buf);
    rv = fwdBlock(mask_lo, mask_hi, chars, buf, low4bits, zeroes);
    if (rv) {
        return rv;
    }

否则,没有匹配的情况下,buf位置并不向前移动16字节,而是移动到与16对齐的地址上。之后,按照16字节对输入数据进行匹配处理,在buf起始地址不是16对齐的情况下,对非对齐部分数据重复处理了。

    buf += (16 - min);

    // Unrolling was here, but it wasn't doing anything but taking up space.
    // Reroll FTW.

    const u8 *last_block = buf_end - 16;
    while (buf < last_block) {
        m128 lchars = load128(buf);
        rv = fwdBlock(mask_lo, mask_hi, lchars, buf, low4bits, zeroes);
        if (rv) {
            return rv;
        }
        buf += 16;
    }

最后,处理输入数据中剩余的小于16字节的数据,但是这里也是按照16字节进行处理,可能对部分数据重复处理了。所有匹配都没有成功,返回输入数据的末尾。

    // Use an unaligned load to mop up the last 16 bytes and get an accurate
    // picture to buf_end.
    assert(buf <= buf_end && buf >= buf_end - 16);
    chars = loadu128(buf_end - 16);
    rv = fwdBlock(mask_lo, mask_hi, chars, buf_end - 16, low4bits, zeroes);
    if (rv) {
        return rv;
    }

    return buf_end;
}

小于16字节匹配

对于不足16字节的输入数据,每个字节的低四位在lo向量中索引取值,高四位在hi向量中索引取值,两次取值进行与操作,如果结果不为零,表示发送匹配,返回匹配位置。

/** \brief Naive byte-by-byte implementation. */
static really_inline
const u8 *shuftiFwdSlow(const u8 *lo, const u8 *hi, const u8 *buf, const u8 *buf_end) {
    assert(buf < buf_end);

    for (; buf < buf_end; ++buf) {
        u8 c = *buf;
        if (lo[c & 0xf] & hi[c >> 4]) {
            break;
        }
    }
    return buf;
}

16字节匹配

函数block的匹配结果(z)如果等于0xffff,表明没有任何匹配发生。否则,将z取反,此时1表示匹配,通过ctz32计算末尾的0的个数,得到匹配发生的字节位置。

static really_inline
const u8 *firstMatch(const u8 *buf, u32 z) {
    if (unlikely(z != 0xffff)) {
        u32 pos = ctz32(~z & 0xffff);
        assert(pos < 16);
        return buf + pos;
    } else {
        return NULL; // no match
    }
}

static really_inline
const u8 *fwdBlock(m128 mask_lo, m128 mask_hi, m128 chars, const u8 *buf,
                   const m128 low4bits, const m128 zeroes) {
    u32 z = block(mask_lo, mask_hi, chars, low4bits, zeroes);

    return firstMatch(buf, z);
}

函数block执行16字节输入数据(chars)的匹配工作。首先,取得chars中每个字节的低四位,在mask_lo向量中执行索引查找;其次,取得chars中每个字节的高四位,在mask_hi向量中执行索引查找。

最后,将两次查找结果执行与操作。与全零值以字节为单位进行比较(eq128),相等的字节比较结果为0xff,不相等结果为0x0。函数movemask128,将16个字节的比较结果压缩为16个比特位返回。

#define GET_LO_4(chars) and128(chars, low4bits)
#define GET_HI_4(chars) rshift64_m128(andnot128(low4bits, chars), 4)

static really_inline
u32 block(m128 mask_lo, m128 mask_hi, m128 chars, const m128 low4bits, const m128 compare) {
    m128 c_lo  = pshufb_m128(mask_lo, GET_LO_4(chars));
    m128 c_hi  = pshufb_m128(mask_hi, GET_HI_4(chars));
    m128 t     = and128(c_lo, c_hi);

    return movemask128(eq128(t, compare));
}

你可能感兴趣的:(shufti)