【发布时间】:2016-01-20 21:40:33
【问题描述】:
[底部加粗的问题]
当汇编器生成二进制编码时,它需要决定是使每个分支长还是短,如果可能的话,短的更好。这部分汇编程序称为分支置换优化(BDO)算法。一种典型的方法是,汇编器使 all 分支编码变短(如果它们小于某个阈值),然后迭代地增加任何分支跳转到未达到的 longs。当然,这可能会导致其他分支转换为跳远。因此,汇编程序必须不断通过跳转列表,直到不再需要升迁。这种二次时间方法对我来说似乎是一种最优算法,但据说 BDO 是 NP 完全的,这种方法实际上并不是最优的。
Randall Hyde 提供了一个反例:
.386
.model flat, syscall
00000000 .code
00000000 _HLAMain proc
00000000 E9 00000016 jmpLbl: jmp [near ptr] target
00000005 = 00000005 jmpSize = $-jmpLbl
00000005 00000016 [ byte 32 - jmpSize*2
dup
(0)
00
]
0000001B target:
0000001B _HLAMain endp
end
通过在括号“[near ptr]”中添加部分并强制使用 5 字节编码,二进制文件实际上最终会更短,因为分配的数组小了两倍的跳转大小。因此,通过缩短跳转编码,最终的代码实际上会更长。
这对我来说似乎是一个非常病态的案例,并且并不真正相关,因为分支编码仍然更小,它只是对程序的非分支部分的这种奇怪的副作用,导致二进制文件变得更大。由于分支编码本身仍然更小,我并不认为这是“start small”算法的有效反例。
我是否可以将 start-small 算法视为最佳 BDO 算法,或者是否存在一种 现实 的情况,即它不为所有分支提供最小编码大小?
【问题讨论】:
-
但是对齐的存在会影响跳跃的大小。无论如何,为了触发 NP 完全性,您需要“异常跳跃” - 首先必须很长的跳跃,然后当其他跳跃变得很长时可以变短。如果你只考虑跳转和无聊的指令,那么这不可能发生。
-
在某处有一些对齐可能意味着在“填充物”上向后的短跳只能在其目标展开之前的一些跳跃之后“成功”,因此扩展一些不需要它的跳跃允许其他跳跃要小。这也不算太疯狂,循环通常是对齐的。
-
@harold:如果有一种方法可以让汇编器通过对先前指令使用更长的编码来对齐,那就太好了(例如,寻址模式或立即数中的 4B 位移,或不必要/重复的前缀),而不是插入 NOP。这对于对齐循环以及 NOP 实际运行的其他情况非常有用。
-
即使这个问题在形式上是 NP 完全的,被优化的例程不是必须非常大才能成为一个重要的问题吗?
-
@j_random_hacker:经典的 LFP 算法是从一个已知太小的近似值开始,然后逐渐添加到它,直到没有任何变化(或者从大开始,然后减去,有时效果会更好)。关键是数据结构:如果您可以在恒定时间内处理加法(包括识别未来加法的候选者),那么算法就是线性时间。 (在另一类解决方案中,候选识别是 log time,最终算法是 n log n。)例如,参见分区细化。
标签: algorithm assembly optimization encoding