1

我正在尝试最大程度地优化一个分支(类似 switch...case),以在 x86 CPU 上模拟 X CPU。我想到了这一点:在内存中,我将加载固定长度为 0x100 字节的 x86 操作码块,如下所示:

first block 
0
...[my code, jump at 0x10000, nop nop nop until 0x9F...]
0x9F
second block 
0x100
...
019F
third block
0x200
...
0x29F
...
etc, etc
...
0x10000

这将是有限的,从内存 $0(+ 可能是一些偏移量)开始并以 $0xFFFF 结束(如 0x10000 大小的“rom”)。现在,每次获取和模拟 X CPU 操作码时,我都会这样做:将其左移 8 位并跳转到该位置。执行此操作,并正常继续我的程序流程。我的问题是:1)这些操作码块是否可能如此紧密?2)这是过去的普遍做法吗?

4

1 回答 1

1

如果您通过 switch 块跨 256 个操作码进行分支,您将进行 CPU 无法很好预测的间接跳转,这将使您在每个操作码上都出现管道中断。

如果模拟操作码的工作规模合理,那么这个管道中断可能并不重要。我怀疑确实如此;“加载寄存器”操作码基本上只是通过内存读取来模拟,这不是很多工作。

您可以通过在 switch 块之前添加特殊测试来购买一些明显的改进,检查两个或三个最常见的操作码(可能是 LOAD、CMP、JMP 条件)[如果操作码 = JMP 然后...] 这些测试 CPU通常可以很好地预测。如果你这样做,测量,测量,测量。

如果可以的话,一个更卑鄙的技巧是在多条指令之间分摊管道中断的成本。如果机器有很多单字节操作码,您可以考虑在接下来的两个操作码字节之间进行 65536 路分支。(现在你必须编写很多 switch case,但它们中的许多本质上是相同的。想知道你的编译器是否可以处理它?)抽象地说,这将管道中断成本降低了两倍。对于具有非常常规指令集的特定机器,这可能不会给您带来很多好处。

但是,您可能没有很多单字节操作码,但您可能需要为每条指令解码一个或多个字节。x86 是这样的(前缀、操作码、MODRM、SIB、偏移量...)。大开关盒应该可以很好地解决这个问题。

在高速缓存行边界上对齐每个开关盒可能会很好。如果指令仿真很简单,则代码很可能适合高速缓存行,因此内存只能看到该高速缓存行的一次提取。如果您不这样做,您的指令仿真将有更高的机会跨越缓存线边界,从而将内存获取成本提高到两个。这对于频繁执行的指令可能无关紧要,但很少执行的指令的代码可能会从缓存中掉出来。当您实际遇到其中之一时,这将有所帮助。

最后一点建议:测量,测量,测量。

于 2015-06-03T14:06:36.240 回答