【问题标题】:What is the minimum number of addressing modes necessary for computation?计算所需的最少寻址模式数是多少?
【发布时间】:2016-05-15 06:24:01
【问题描述】:

在 x86 汇编器中,假设你有

  • 立即寻址模式用于分配号码
  • 寄存器寻址模式用于寄存器
  • 直接寻址模式用于内存地址,

为什么需要索引和基指针寻址模式? 据我所知,每个都可以用循环替换。

另外,间接模式似乎也不是很有用,因为您可以简单地使用直接模式来引用内存地址。首先访问一个寄存器然后包含一个指向内存地址的指针的目的是什么?

简而言之,哪些寻址模式是真正需要的?

【问题讨论】:

  • 有很多东西可以换成别的东西。如果你有add,为什么还需要incsub。但是,除非您使用自修改代码,否则间接模式不容易被替换。尝试在没有它的情况下实现指针。

标签: assembly x86 cpu-registers cpu-architecture addressing


【解决方案1】:

虽然理论上“寻址模式”可以用来指代操作数类型,但由于它不涉及地址,所以有点令人困惑。 Intel手册中使用'addressing mode'来指代内存寻址,我将使用这个定义。

在汇编中,操作数可以是:

  • 立即值
  • 一个寄存器
  • 内存中的值(这里的操作数是地址)

在x86架构中,“寻址方式”只针对最后一种操作数:内存操作数(地址),是指可用于计算地址的方法。寻址模式可以概括为一个可配置的寻址模式:

address = REG_base + REG_index*n + offset

REG_baseREG_indexnoffset 都是可配置的,并且都可以省略(但显然你至少需要一个)。

address = offset 称为立即、直接或绝对寻址。
address = REG_base 称为寄存器间接寻址。
address = REG_base + REG_index 称为基址加索引寻址。
同样,您可以添加偏移量 (offset) 和比例 (n)。

严格来说,您只需要一种模式即可完成所有操作:注册间接寻址 (address = REG)。这样,如果您需要访问内存,您可以在寄存器中计算您想要的任何地址,并使用它来进行访问。 它还可以通过使用内存来代替直接寄存器操作数,并通过用算术构造值来替换立即操作数。但是,对于实际的指令集,您仍然需要立即操作数来有效地加载地址,如果您不想要仅指针的寄存器,则需要寄存器操作数。

为了方便起见,除了寄存器间接之外的所有其他寻址模式都在这里,它们确实很方便:

  • 如果您只需要访问内存中的固定变量,立即寻址可以为您节省一个寄存器。
  • Base + offset 对于访问对象成员非常有用:您可以将基地址保存在寄存器中并使用固定偏移量访问各个成员。无需中间计算或注册来保存会员地址。
  • 类似地,索引寻址用于访问数组:您只需更改索引寄存器即可访问数组中的任何值。
  • 使用比例尺,您可以访问多字节变量(例如:int)数组,而无需额外的寄存器或计算。
  • 所有内容的组合都可用于访问对象中的数组成员,同时仍保留基指针以供可能访问对象中的其他成员。

这些寻址模式不需要 CPU 进行太多计算:只需要加法和移位。考虑到 x86 可以在每个循环中进行一次乘法运算,这些操作虽然很简单,但仍然非常方便。

【讨论】:

  • 在计算机体系结构的典型教学环境中,立即数和寄存器被认为是寻址模式(即使它们不寻址内存)。另外,我认为任何 x86 实现都没有单周期 latency 用于乘法; x86 实现通常是流水线乘法,因此可以在每个周期开始一个新的(独立的)乘法,但是每个周期进行一次乘法和在一个周期中进行一次乘法之间存在差异。
  • @PaulA.Clayton 可以说,不涉及地址的模式不被视为寻址模式。至少我认为他们不应该。没有涉及地址,它会产生像这个问题这样的误解。 x86 在一个周期内进行乘法运算;指令读取、加载和存储的其他周期不用于乘法。这在这里很重要,因为在执行指令中的附加计算(例如地址计算)时,您没有这些成本。
  • @ElderBug:大多数当前的 x86 CPU 可以以每时钟 1 倍的吞吐量成倍增长,但没有一个能够以一个周期 延迟 做到这一点。如果一个乘法的输入是前一个乘法的输出,则只能维持每 3 个时钟(英特尔 SnB 系列)。有关 insn 表,请参阅 agner.org/optimize。这些表中延迟最低的 x86 乘法是 Via Nano3000(又名 Isaiah),mul r8imul r32, r32 的延迟为 2c。大多数 x86 CPU 可以在一个周期内移位,但不能进行移位加法运算。寻址模式也有明显的代码大小/密度/寄存器稀缺性优势
  • @PeterCordes 你说的没错,但正如我已经说过的,在指令中进行额外计算时(对于简单的情况),延迟几乎是无关紧要的。大多数延迟并非来自计算本身。我的意思是,您可以在不影响延迟(或次要影响)的情况下使用任意乘法的寻址模式。此外,大多数 x86 CPU 实际上可以使用 lea 在单个周期内执行简单的移位和加法操作:)。
  • 我的意思是“没有lea”,如果没有寻址模式,它无论如何都不会这样做。现在重读,确实明显错了,所以我写的和我的意思不符。 :( Re: mul 的延迟:是的,3c 延迟确实来自计算本身。add 具有 1c 延迟:您可以 add eax, eax 吞吐量(受延迟限制)为 1 @在 Intel SnB 上,每时钟 987654340@,但只有 imul eax, eax,每 3 个时钟的吞吐量为 1imul。如果您使用,像 [reg1*reg2] 这样的寻址模式可能会在使用指针追踪测量的延迟上增加 2 个时钟它。
【解决方案2】:

x86 不能没有寄存器做很多事情,所以我不认为你可以摆脱寄存器“寻址模式”。一些非常不同的架构可能不使用寄存器,而只有堆栈或内存、内存指令。 IDK 他们如何实现指针;也许这样的架构可以做到memory[memory](C 数组表示法)。

可能不需要立即计算。您可以使用多个寄存器构造任何值。从零开始(xor eax, eax),inc 得到 1,左移到你想要的任何位置,inc 设置低位,左移等等。所以它需要最糟糕的2*popcount(N) 指令将N 放入寄存器。请注意,即时移位计数将不可用,因此重复移位一个的明显方法(shl eax,是的,移位一个单独的编码,或者只使用add eax, eax)将取决于在最高设置位的位置。所以log2(N) + popcount(N) 是显而易见的转变和公司。

绝对(你称之为直接)内存寻址不是最有用的寻址模式。我们可以通过使用一系列指令(见上文)构造地址并使用[register] 来模拟它。如果我们想减少,我们想放弃它。正如 Jester 所指出的,将绝对寻址作为我们唯一的形式使用起来会非常不方便(或者可能是不可能的?)。

索引显然可用于性能,而不是必需:您可以使用单独的指令进行移位和添加。

置换也只是为了性能,所以我们可以去掉它们并强制代码手动添加任何置换。请参阅紧接段落了解如何操作。


我相信 x86 仍然可以使用 just register[register] 寻址模式任意编程。

使用register[register]immediate,性能应该不会比完整的x86差多少。


如果对内存的隐式访问不算作寻址模式,你当然可以用lodsdstosd 模拟[register],但你将无法进行原子读-修改-写操作.不过,这感觉像是作弊。

还有堆栈 (push/pop):我不知道堆栈+寄存器机器是否是图灵完备的,但它肯定不是通常意义上的可编程。当然,如果你修改e/rsp,你可以再次模拟[register],但操作数大小的选择比lodsb/w/d/q/stosb/w/d/q少。

如果包含 16 个 ymm 寄存器,x86 有相当多的空间可以在寄存器中存储东西。虽然我想不出一种在不使用内存或立即操作数(vextractf128)的情况下在整数寄存器和 ymm 的高 128b 之间移动数据的方法,但实际上你有 16 个 16B 向量寄存器插槽用于存储堆栈以外的本地状态。尽管如此,它的大小还是有限的,这可能意味着 32 位 386 ISA 中的 8 个 GP 寄存器与 64 位 AVX2 ISA 中的所有整数/mmx/ymm 寄存器与机器是否图灵完备无关,只有 push/pop , 寄存器,并且除了通过 push/pop 之外没有修改堆栈指针。

【讨论】:

    猜你喜欢
    • 2015-02-25
    • 2011-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多