- 第一章 CPU 简介
- 第二章 Hello,world!
- 第三章 函数开始和结束
- 第四章 栈
- Chapter 5 printf() 与参数处理
- Chapter 6 scanf()
- CHAPER7 访问传递参数
- Chapter 8 一个或者多个字的返回值
- Chapter 9 指针
- Chapter 10 条件跳转
- 第 11 章 选择结构 switch()/case/default
- 第 12 章 循环结构
- 第 13 章 strlen()
- Chapter 14 Division by 9
- chapter 15 用 FPU 工作
- Chapter 16 数组
- Chapter 17 位域
- 第 18 章 结构体
- 19 章 联合体
- 第二十章 函数指针
- 第 21 章 在 32 位环境中的 64 位值
- 第二十二章 SIMD
- 23 章 64 位化
- 24 章 使用 x64 下的 SIMD 来处理浮点数
- 25 章 温度转换
- 26 章 C99 的限制
- 27 章 内联函数
- 第 28 章 得到不正确反汇编结果
- 第 29 章 花指令
- 第 30 章 16 位 Windows
- 第 31 章 类
- 三十二 ostream
- 34.2.2 MSVC
- 34.2.3 C++ 11 std::forward_list
- 34.3 std::vector
- 34.4 std::map and std::set
22.2 SIMD strlen() implementation
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) 提供了更多的指令,这些可能更容易字符串操作。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论