为什么MSVC在执行此位测试之前会发出无用的MOVSX

Why does MSVC emit a useless MOVSX before performing this Bit Test?

本文关键字:无用 MOVSX MSVC 执行 测试 为什么      更新时间:2023-10-16

在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对在al44之间执行无符号比较,如果al较大,则向前分支。

现在,我们知道了0 <= al <= 44的无符号数。因此,al的最高比特不可能被设置!

尽管如此,下一条指令是movsx rax, al。这是一个延伸动作的标志。但自:

  • alrax的最低字节
  • 我们已经知道rax的其他7个字节为零
  • 我们刚刚证明了al的最高比特不可能被设置

该CCD_ 17必须是no-op。

MSVC为什么这么做?我假设这不是为了填充,因为在这种情况下,另一个npad会使含义更清晰。是刷新数据依赖项还是其他什么?

(顺便说一句,这个bt优化真的让我很高兴。一些有趣的事实:它运行的时间是你所期望的4对cmp/je的0.6倍,它比strspnstd::string::find_first_not_of快,而且它只发生在64位构建中,即使感兴趣的字符的值低于32。)

你肯定知道这个优化由优化器中查找模式的特定代码生成。只是比特掩码的生成就泄露了它。是的,好把戏。

这里有两个基本的代码生成案例。第一个是更通用的,其中(charmax-charmin<=64)但charmax>=64。优化器需要从您看到的代码中生成不同的代码,它需要减去charmin。该版本的没有MOVSX指令。您可以通过将*s == ' '替换为*s == 'A'来查看它。

然后是您测试的特殊情况,所有要测试的字符代码恰好是<64.微软程序员确实在他的代码中处理了这个问题,他确保不会生成愚蠢的SUBEAX,0指令。但是忽略了生成MOVSX是不必要的。在一般情况下,只检查最佳代码肯定会错过。代码中的一个通用函数调用很容易被忽略,请注意使用/J编译时指令是如何更改为MOVZX的。否则很容易被认为是必要的,没有BT指令将8位寄存器作为第二个操作数,因此AL寄存器的负载本身不够。

可能存在一个假设的后优化器,用于优化优化器生成的优化代码。并决定保留MOVSX以改进超标量执行。我严重怀疑它的存在。

正如Hans-Passant已经提到的,您的代码是优化的特殊情况。我没有查看编译器/优化器的来源,所以我只能说我预计会发生什么。然而,以下是我对此的解释。

您在C/C++中的代码:

while (*s == ' ' || *s == ',' || *s == 'r' || *s == 'n') {
    ++s;
}

相当于:

while (*s == 32 || *s == 44 || *s == 13 || *s == 12) {
    ++s;
}

或:

while (*s == 12 || *s == 13 || *s == 32 || *s == 44) {
   ++s;
}

优化器检测到存在同一字符的多次(>=3次)比较的"if"。现在,优化器根据需要生成尽可能多的64位模式(对于8位字符,最多4个模式,64*4=>所有256个可能的值)。每个比特模式都有一个偏移量(=模式中第一个比特对应的测试值),需要从测试值中减去。在您的情况下,对于范围从12到44的值,只需要一个64位模式。所以你的代码会被转换成一些通用代码,比如:

#define ranged_bittest(pattern, value, min_val, max_val) 
  (val >= min_val) && (val <= max_val) && BT_with_offset(pattern, val, min_val)
while ( !ranged_bittest(<BITPATTERN>, *s, 12, 44) ) {
    ++s;
}

现在,一些其他优化采用ranged_bittest(<BITPATTERN>, *s, 12, 44);,因为它检测到具有起始偏移12的位测试和可以安全地向左移位12位的模式(因为模式的高12位为零)。ranged_bittest(<BITPATTERN>, *s, 12, 44)=>ranged_bittest(<BITPATTERN> << 12, *s, 0, 44)

这演变为:

while (!(val >= 0 && val <= 44 && BT_with_offset(<SHIFTEDBITPATTERN>, *s, 0))) {
    ++s;
}

val >= 0 &&被优化掉了(因为它被认为总是真的),"while"被翻译成一些带有中断的"do"循环;(在大多数情况下,这对分支预测更好)导致:

do {
  if (val > 44) break;
  if (BT_with_offset(<SHIFTEDBITPATTERN>, *s, 0)) break;
  ++s;
} while (true);

无论出于何种原因,优化都会在此停止(例如,由于性能原因,对代码片段应用进一步优化的频率有限制)。

现在正在组装生产线

if (BT_with_offset(<SHIFTEDBITPATTERN>, *s, 0)) break;`

被翻译成类似于:

MOV <reg64_A>, const_pattern
MOV <reg64_B>, value
SUB <reg64_B>, const_offset
BT <reg64_A>, <reg64_B>

装配优化器减少:

MOV <reg64_B>, value
SUB <reg64_B>, 0

MOVSX <reg64_B>, value

在您的特殊情况下,这是:

MOVSX rax, al

程序集优化器(不知何故)没有足够的信息来完全消除符号扩展。也许程序集优化器相当"愚蠢"(不能做任何逻辑表达式优化之类的事情,只是简单的替换),或者完整的优化级别还没有激活。

因此,它不能删除movsx rax,al,因为在代码的这一点上,它没有任何关于raxal的可能值的元信息。

我不知道,如果这是真的,但这是我对这个案件的最佳猜测。。。

当我第一次看到这段代码时,最让我印象深刻的是它的优化效果有多差
是的,使用64位寄存器作为查找表是一个巧妙的技巧,但是。。。

  • 为什么仍然使用INC而不是更好的ADD,1
  • 如果BT指令的目标操作数是寄存器,那么当我们知道BT指令使用其源操作数MOD 64时,为什么要使用CMP ... JA ... MOVSX ...(因此有58位不需要考虑)
  • 为什么不从条件分支被预测会倒退这一事实中受益呢
  • 为什么不减少分支机构的数量

我认为一个真正的汇编程序程序员会写得更像这样:

  mov     rcx, 0FFFFEFFEFFFFDBFFh  ;~0000100100002400h
  sub     rsi, 1
  npad    1
$LL82@myFunc:
  add     rsi, 1
  movzx   eax, BYTE PTR [rsi]   ;mov al,[rsi]
  test    al, 11000000b
  setz    bl
  test    bl, bl
  bt      rcx, rax
  ja      SHORT $LL82@myFunc

如果(CF或ZF)=0 ,则ja跳转

  • ZF=1表示AL=[64255],不关心CF
  • 对于AL=[0.63],ZF=0;对于AL={10,13,32,44},CF=0

对于[64255]范围内的所有ASCII,test al, 11000000b指令将给出非零结果(ZF=0)。因为组合setz bl test bl, bl随后用于将ZF翻转为1,所以ja指令不再有任何机会继续循环
相反,对于[0.63]范围内的所有ASCII,ZF最终将为0,从而允许ja完全解释从bt rcx, rax指令获得的CF。

也许我们对优化编译器抱有很大期望