您可以使用 16 位 8 位循环交换内存中的两个字节:
rol word ptr [ebp], 8 ; byte [ebp] becomes byte [ebp+1], and vice verse
但是,如果您已经在寄存器中有字节(例如,因为您加载了它们以便可以比较它们),那么从寄存器中存储它们可能会更好。
由于您只需要存储寄存器的低字节,因此您需要使用字节存储al
或bl
不使用双字存储edi
! 更改您的寄存器分配,使您的字节位于 AL、BL、CL 或 DL 之一中,而不是 EDI 的低字节中。只有 x86-64 可以访问 EDI 的低字节(作为 DIL)。使用 EDI 作为索引。(然后名称 EDI 来自目标索引)。或者,使用指针增量代替 base+index,以便 EDI 指向您可能想要交换的当前字节或字节对。
因此:
movzx eax, byte ptr [edi + ebp] ; load the 1st byte
movzx edx, byte ptr [edi + ebp + 1] ; load the 2nd byte
cmp al, dl
jae noswap
mov [edi+ebp], dl ; opposite of how you loaded them
mov [edi+ebp+1], al
noswap:
inc edi
... loop logic
movzx
避免了对不将 AL 与整个 EAX 分开重命名的 CPU 上的 EAX 旧值的错误依赖。如果你完成了 mov al, [edi + ebp]
,一些 CPU 会将 EAX 的旧值作为该指令的另一个输入依赖项。
请注意,如果您实际上是在实现冒泡排序(eww,yuck),则每次迭代只需执行一次加载。您总是有两个值之一要在寄存器中进行比较。您可以为第一次迭代设置循环外的负载。
如果您正在执行 16 位加载,然后仅比较低字节(例如,作为排序的一部分),那么您完全可以交换低 2 字节ebx
并将其存储回来:
movzx eax, word ptr [edi+ebp]
cmp ah, al ; compare the low 2 bytes of EAX with each other
jae noswap
rol ax, 8 ; swap AL with AH. This is more efficient than xchg al,ah or two MOV stores.
mov [edi+ebp], ax
noswap:
这本身很好,但是与下一个 16 位加载部分重叠的 16 位存储有点糟糕。(商店转发摊位)。仅将新字节加载到 AH 中(将旧字节保留在 AL 中)也不是很好。在现代英特尔 CPU 上读取 AH 时,这将暂停一个周期以合并,并创建一个依赖链。
为什么我使用[edi+ebp]
而不是[ebp+edi]
?它节省了一个字节以使 EBP 成为索引寄存器,因为[EBP + EDI*1]
没有位移是不可编码的。NASM 和 YASM 不会为您交换,因为 base=EBP 意味着 SS 段。但是假设一个平坦的内存模型,没关系,所以我们可以手动进行优化。
有关的:
对 32 位int
元素的冒泡排序。 x86(masm32)中的冒泡排序,我写的排序不起作用
8 位元素的 BubbleSort,在 19 个字节的 16 位 x86 机器代码中:https ://codegolf.stackexchange.com/questions/77836/sort-an-integer-list/149038#149038 和 JumpDown 排序32 位元素。
装配 - 用于排序字符串的冒泡排序(对字符串中的字符进行排序)
装配冒泡排序交换
一旦您了解 x86 部分寄存器的工作原理,在 8 位和 32 位元素之间进行更改只需更改寄存器名称并将增量从 1 更改为 4。