SUB 然后无符号比较是仅使用一个条件分支检查输入是否在特定范围内的好方法,而不是单独的比较和分支>= 'A'
and <= 'Z'
.
编译器尽可能使用这个技巧。也可以看看Agner Fog 的优化装配指南,以及其他链接x86标记 wiki 以获取有关编写高效 asm 的更多内容。
您甚至可以使用它通过一个分支来检测字母字符(小写或大写):OR 与 0x20 将使任何大写字符变为小写,但不会使任何非字母字符成为字母字符。这样做,然后使用无符号比较技巧来检查是否在小写范围内。 (或者以 AND 开头~0x20
清除该位,强制大写)。我用了这个技巧关于翻转字母字符大小写而保留其他字符的答案.
是的,正如您所注意到的,ASCII 的设计使每个字母的大写/小写之间的差异只是翻转一位。每个小写字符都设置了 0x20,而大写字符则将其清除。 AND/OR/XOR 通常更适合执行此操作(相对于 ADD/SUB),因为在强制采用一种情况时,有时您可以利用不关心初始状态的优势。
你的代码有一些奇怪的东西:PUSH AL
甚至不能用大多数汇编器进行汇编,因为压入/弹出的最小大小是 16 位。保存/恢复 AL 也没有意义,因为在循环后恢复 AL 后,您会破坏整个 EAX!
此外,MOV 只是覆盖其目的地,因此无需xor bl,bl
.
另外,您使用 BL 作为暂存寄存器,但它是 EBX 的低字节(您将其用作指针!)
以下是我的做法,仅使用 EAX、ECX 和 EDX,这样我就不必保存/恢复任何寄存器。 (您的函数会破坏 EBX,大多数 32 位和 64 位调用约定都需要函数来保存/恢复)。如果我需要一个额外的寄存器string
不是静态分配的,让我可以使用它的地址作为直接常量。
toLower2 PROC ;65-90 is upper, 97-122 is lower (XOR 32?)
mov edx, OFFSET string ; don't need LEA for this, and mov is slightly more efficient
add edx, strSize ; This should really be an equ definition, not a load from memory.
; edx starts at one-past-the-end, and we loop back to the start
loop1:
dec edx
movzx eax, byte [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too, but this avoids a partial-register stall when doing the mov+sub in one instruction with LEA
lea ecx, [eax - 'A'] ; cl = al-'A', and we don't care about the rest of the register
cmp cl, 25 ;if(c >= 'A' && c <= 'Z') c |= 0x20;
ja NoCap
or al, 0x20 ; tolower
mov [edx], al ; since we're branching anyway, make the store conditional
NoCap:
cmp edx, OFFSET string
ja loop1
mov eax, edx
toLower2 ENDP
LOOP指令速度慢,应该避免。忘记它甚至存在并使用任何方便的循环条件。
仅在字符更改时才进行存储会使代码更加高效,因为如果无事可做,在一段时间没有更改的内存上使用它不会弄脏缓存。
代替ja NoCap
,你可以用 cmov 无分支地做到这一点。但现在我必须忽略我的建议,更喜欢 AND/OR 而不是 ADD/SUB,因为我们可以使用 LEA 添加 0x20 而不影响标志,从而为我们节省一个寄存器。
loop1:
dec edx
movzx eax, byte [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too, but this avoids a partial-register stall when doing the mov+sub in one instruction with LEA
lea ecx, [eax - 'A'] ; cl = al-'A', and we don't care about the rest of the register
cmp cl, 25 ;if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 0x20] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the uppercase range to start with
; al = tolower(al)
mov [edx], al
cmp edx, OFFSET string
ja loop1