読者です 読者をやめる 読者になる 読者になる

Counting UTF-8 characters with word

Char

こちらも search non ascii 同様にワード単位で見れば早くなります。具体的には、そもそも UTF-8 は trail byte が [\x80-\xBF] に限定され、またこの範囲は lead byte には出現しません。つまり、バイト列の中から、0b10xxxxxx 以外のバイトの数を数えれば、それがそのまま文字列長になります。これをワード単位で並列して実行する場合は、バイトの最上位ビットの否定と、2番目のビットの論理和をとり、それが真なものを数えれば大丈夫です。

範囲 2進 最上位ビット 最上位ビットの否定 2番目 論理和
\x00-\x7F 0b0x0000 0 1 x 1 OR x = 1
\x80-\xBF 0b100000 1 0 0 0 OR 0 = 0
\xC0-\xFF 0b000000 1 1 0 1 OR 0 = 1
#if SIZEOF_VOIDP == 8
# define NONASCII_MASK 0x8080808080808080LL
#elif SIZEOF_VOIDP == 4
# define NONASCII_MASK 0x80808080UL
#endif
#define is_utf8_lead_byte(c) (((c)&0xC0) != 0x80)

static inline uintptr_t
count_utf8_lead_bytes_with_word(const uintptr_t *s)
{
    uintptr_t d = *s;
    d |= ~(d>>1);
    d >>= 6;
    d &= NONASCII_MASK >> 7;
    d += (d>>8);
    d += (d>>16);
#if SIZEOF_VOIDP == 8
    d += (d>>32);
#endif
    return (d&0xF);
}

const int
utf8_length(const char *p, const char *e)
{
    intptr_t align;
    if (SIZEOF_VOIDP * 2 <= e - p) {
	const intptr_t lowbits = SIZEOF_VOIDP - 1;
	const intptr_t *wp, *we;
	wp = (const intptr_t *)(~lowbits & ((intptr_t)p + lowbits));
	we = (const intptr_t *)(~lowbits & (intptr_t)e);
	for (; p < (const char *)wp; ++p) {
	    if (is_utf8_lead_byte(*p)) len++;
	}
	for (; wp < we; ++wp) {
	    if (*wp & NONASCII_MASK) break;
	}
	p = (const char *)wp;
    }
    for (; p < e; ++p) {
	if (is_utf8_lead_byte(*p)) len++;
    }
    return len;
}