返回介绍

22.2 SIMD strlen() implementation

发布于 2025-02-22 14:00:48 字数 4720 浏览 0 评论 0 收藏 0

SIMD 指令可能通过特殊的宏 8 插入到 C/C++代码中。MSVC 中他们被保存在 intrin.h 中。

Strlen() 函数 9 的实现使用了 SIMD 指令,比常规的实现快了 2-2.5 倍。该函数将 16 个字符加载到一个 XMM 寄存器并检查是否为零

#!cpp
size_t strlen_sse2(const char *str)
{
        register size_t len = 0;
        const char *s=str;
        bool str_is_aligned=(((unsigned int)str)&0xFFFFFFF0) == (unsigned int)str;

        if (str_is_aligned==false)
                return strlen (str);

        __m128i xmm0 = _mm_setzero_si128();
        __m128i xmm1;
        int mask = 0;

        for (;;)
        {
                xmm1 = _mm_load_si128((__m128i *)s);
                xmm1 = _mm_cmpeq_epi8(xmm1, xmm0);
                if ((mask = _mm_movemask_epi8(xmm1)) != 0)
                {
                        unsigned long pos;
                        _BitScanForward(&pos, mask);
                        len += (size_t)pos;
                        break;
                }
                s += sizeof(__m128i);
                len += sizeof(__m128i);
        };

        return len;
}

(这里的例子基于源代码).

MSVC 2010 /Ox 编译选项:

#!bash
_pos$75552 = -4                 ; size = 4
_str$ = 8                       ; size = 4
?strlen_sse2@@YAIPBD@Z PROC     ; strlen_sse2

    push    ebp
    mov     ebp, esp
    and     esp, -16 ; fffffff0H
    mov     eax, DWORD PTR _str$[ebp]
    sub     esp, 12 ; 0000000cH
    push    esi
    mov     esi, eax
    and     esi, -16 ; fffffff0H
    xor     edx, edx
    mov     ecx, eax
    cmp     esi, eax
    je      SHORT $LN4@strlen_sse
    lea     edx, DWORD PTR [eax+1]
    npad    3
$LL11@strlen_sse:
    mov     cl, BYTE PTR [eax]
    inc     eax
    test    cl, cl
    jne     SHORT $LL11@strlen_sse
    sub     eax, edx
    pop     esi
    mov     esp, ebp
    pop     ebp
    ret     0
$LN4@strlen_sse:
    movdqa  xmm1, XMMWORD PTR [eax]
    pxor    xmm0, xmm0
    pcmpeqb         xmm1, xmm0
    pmovmskb        eax, xmm1
    test    eax, eax
    jne     SHORT $LN9@strlen_sse
$LL3@strlen_sse:
    movdqa  xmm1, XMMWORD PTR [ecx+16]
    add     ecx, 16 ; 00000010H
    pcmpeqb         xmm1, xmm0
    add     edx, 16 ; 00000010H
    pmovmskb        eax, xmm1
    test    eax, eax
    je      SHORT $LL3@strlen_sse
$LN9@strlen_sse:
    bsf     eax, eax
    mov     ecx, eax
    mov     DWORD PTR _pos$75552[esp+16], eax
    lea     eax, DWORD PTR [ecx+edx]
    pop     esi
    mov     esp, ebp
    pop     ebp
    ret     0
?strlen_sse2@@YAIPBD@Z ENDP ; strlen_sse2

首先,检查 str 指针,如果不是按照 16 字节对齐则调用常规实现。

然后使用 movdqa 指令加载 16 个字节到 xmm1.这里不使用 movdqu 的原因是如果指针不一致则从内存中加载的数据可能会不一致。

是的,它可能会以这种方式做,如果指针对齐,使用 MOVDQA 加载数据,否则使用比较慢的 MOVDQU。

但是我们应该注意到这样的警告:

在 windowsNT 操作系统但不限于该操作系统,内存页按 4kb 对齐。每个 win32 进程独占 4GB 虚拟内存。事实上,只有部分地址空间与真实物理内存对应,如果进程访问的内存没有对应物理内存,将触发异常。这是虚拟内存的工作方式 10.

一个函数一次加载 16 个字节,可能会跨内存分块访问。我们考虑这样一种情况,操作系统在 x008c0000 分配 8192(0x2000)字节,因此块字节从地质 0x008c0000 到 0x008c1fff。 内存块之后从 0x008c2000 什么都没有,操作系统没有分配任何内存。访问该地址将触发异常。

假如内存块包含的最后 5 个字符如下:

0x008c1ff8      ’h’
0x008c1ff9      ’e’
0x008c1ffa      ’l’
0x008c1ffb      ’l’
0x008c1ffc      ’o’
0x008c1ffd      ’x00’
0x008c1ffe      random noise
0x008c1fff      random noise

正常情况下,strlen() 只会读取到”hello”。

如果我们使用 MOVDQU 读取 16 个字节,将会触发异常,应该避免这种情况。

因为我们要确保 16 字节对齐,保证我们不会读取未分配的内存。

让我们回到函数:

_mm_setzero_si128()—宏 pxor xmm0, xmm0—清空 XMM0 寄存器。
_mm_load_si128()—宏 MOVDQA, 从内存加载 16 个字节到 XMM 寄存器。
_mm_cmpeq_epi8()—宏 PCMPEQB,比较 XMM 寄存器的字节位,如果相等则为 0xff 否则为 0。

比如:

XMM1: 11223344556677880000000000000000
XMM0: 11ab3444007877881111111111111111

执行 pcmpeqb xmm1, xmm0 之后,XMM1 寄存器的值为:

XMM1: ff0000ff0000ffff0000000000000000

在本例中该指令比较每一个 16 字节块与 16 字节 0 字节块对比,XMM0 通过 pxor xmm0 xmm0 置零。

接下来宏_mm_movemask_epi8() —这是 PMOVMSKB 指令。

pmovmskb eax, xmm1

pmovmskb 创建源操作数每一个字节的自高位掩码,并保存结果到目的操作数的低 byte。源操作数必须为 MMX 寄存器,目的操作数必须为 32 位通用寄存器。

比如:

XMM1: 0000ff00000000000000ff0000000000

对应的 EAX:

EAX=0010000000100000b

之后 bsf eax,eax 被执行,eax 值为 5,意味着第一个是 1 的位置是 5(从 0 开始)。

MSVC 关于这个指令的宏是:_BitScanForward.

至此,找到结尾 0 的位置,然后程序返回长度计数。

整个过程大致就是这样。

顺便提一下,MSVC 为了优化,使用了两个并排的循环。

SSE 4.2(英特尔 core i7) 提供了更多的指令,这些可能更容易字符串操作。

http://www.strchr.com/strcmp_and_strlen_using_sse_4.2

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文