在MSVC 2013的64位版本中,使用/O2
优化编译以下代码:
while (*s == ' ' || *s == ',' || *s == '\r' || *s == '\n') {
++s;
}
我拿到了下面这段代码 - 它使用64位寄存器作为查找表,并使用 bt
(位测试) 指令进行了非常酷的优化。
mov rcx, 17596481020928 ; 0000100100002400H
npad 5
$LL82@myFunc:
movzx eax, BYTE PTR [rsi]
cmp al, 44 ; 0000002cH
ja SHORT $LN81@myFunc
movsx rax, al
bt rcx, rax
jae SHORT $LN81@myFunc
inc rsi
jmp SHORT $LL82@myFunc
$LN81@myFunc:
; code after loop...
但我的问题是:在第一个分支之后,movsx rax, al
的目的是什么?
首先,我们将字符串中的一个字节加载到rax
中,并进行零扩展:
movzx eax, BYTE PTR [rsi]
cmp
/ja
指令对al
和44
进行无符号比较,若al
大于44
则跳转。
因此我们知道al
在无符号数中满足0 <= al <= 44
。 因此,al
的最高位肯定不会被设置!
尽管如此,下一条指令是movsx rax, al
,这是一个符号扩展的移动指令。但由于:
al
是rax
的最低字节- 我们已经知道了
rax
的其他7个字节为零 - 我们刚刚证明了
al
的最高位肯定不会被设置
所以这个movsx
必须是无操作的。
MSVC为什么要这样做?我认为这不是为了填充空间,因为在这种情况下另一个npad
会使含义更清晰。 是为了刷新数据依赖关系或其他原因吗?
(顺便说一句,这个bt
优化真的让我很高兴。一些有趣的事实:它的运行时间是你可能期望的4个cmp
/je
指令对的0.6倍,比strspn
或std::string::find_first_not_of
快得多,并且只会在64位构建中发生,即使感兴趣的字符值小于32。)
movsx
之前它们不是零,那么在该指令之后它们肯定是零。在movzx eax
之前的指令只能将其零扩展到32位,而movsx rax
指令则将其符号扩展到64位(即剩余的4个字节)吗?如果这不正确,请提前谅解;正如我所说,我已经有一段时间没有深入研究了。 - WhozCraig