具体是:
mov %eax, %ds
比
慢mov %eax, %ebx
还是它们的速度相同。我已经在网上进行了调查,但无法找到确切的答案。
我不确定这是否是一个愚蠢的问题,但是我认为可以修改分段寄存器可以使处理器做更多的工作。
N.B我关心的是旧的x86 linux cpus,而不是现代的x86_64 cpus,因为分段的工作原理不同。
答案 0 :(得分:5)
mov %eax, %ebx
是最通用指令之一。现代硬件非常有效地支持它,通常会有不适用于其他任何指令的特殊情况。在较旧的硬件上,它始终是最便宜的说明之一。
在Ivybridge及更高版本上,它甚至不需要执行单元,并且延迟为零。它在寄存器重命名阶段处理。 Can x86's MOV really be "free"? Why can't I reproduce this at all?即使在较早的CPU上,任何ALU端口也只有1个uop(因此每个时钟吞吐量通常为3或4)。
在AMD Piledriver / Steamroller上,mov r32,r32
和r64,r64可以在AGU端口以及ALU端口上运行,使其每个时钟吞吐量为4,而添加或mov
每个时钟为2在8位或16位寄存器上(必须合并到目标寄存器中)。
mov
到段reg是相当少见的指令。但是,它是内核对每个系统调用(甚至可能是中断)所做的工作的一部分,因此使其高效将加快系统调用和I / O密集型工作负载的快速路径。因此,即使它只出现在少数几个地方,它也可以运行很多。但是与mov r,r
相比,它的重要性仍然很小!
mov
到段reg的速度很慢:它会触发GDT或LDT的加载以更新描述符缓存,因此已对其进行了微编码。
即使在x86-64长模式下也是如此; the GDT entry中的段基础/限制字段将被忽略,但是它仍然必须与the segment descriptor中的其他字段一起更新描述符缓存,包括确实适用于数据段的DPL(描述符特权级别)。 / p>
Agner Fog's instruction tables列出了Nehalem和早期CPU的mov sr, r
(Intel synax,移动到网段reg)的uop计数和吞吐量。他停止为以后的CPU测试seg reg,因为它晦涩难懂,没有被编译器(或人工手动优化)使用,但是SnB系列的计数可能有些相似。 (InstLatx64也不测试段规则,例如不在此Sandybridge instruction-timing test中)
MOV sr,r
在Nehalem上(大概在保护模式或长模式下进行了测试):
其他CPU相似:
奔腾4:4微码+ 4微码,吞吐量为14c。
延迟= 12c 16位实模式或vm86模式,24c在32位保护模式下。 12c是他在主表中列出的内容,因此大概他在其他CPU上的等待时间数字也是实模式等待时间,在其中写一个段reg只会设置base = sreg<<4
。)
在P4上,读取段reg的速度很慢,与其他CPU不同:4 uops + 4微码,6c吞吐量
P4 Prescott:1 uop + 8微码。 27c吞吐量。读取段reg = 8c吞吐量。
奔腾M:p0为8微秒,与PIII相同。
Conroe / Merom和Wolfdale / Penryn(第一代和第二代Core2):8个融合域指令,4个ALU(p015),4个负载/ AGU(p2)。每16个周期1个吞吐量,这是Agner测试过的所有CPU中最慢的。
Skylake(我的测试使用在循环外读取的值重新加载它们):在仅dec / jnz的循环中:10个融合域uops(前端),6未融合域(执行单位)。每18c吞吐量一个。
在循环中使用相同的选择器写入4个不同段reg(ds / es / fs / gs):每25c四个mov
吞吐量,6个融合/未融合域。 (也许有些取消了?)
在一次循环中写入ds
4次::每72c进行一次迭代(每18c进行一次mov ds,eax
)。相同的uop计数:每个mov
约有6个已融合和未融合。
这似乎表明Skylake不会不重命名段reg:必须先完成对一个段的写操作。
K7 / K8 / K10:6次“操作”,吞吐量为8c。
Atom:7 oups,21c吞吐量
mov r, sr
)。没有列出延迟,这很奇怪。也许他是根据何时可以将它用于负载来衡量段写入延迟的?像mov eax, [ebx]
/ mov ds, eax
循环播放?
有序奔腾(P5 / PMMX)的mov-to-sr便宜:Agner列出了“> = 2个周期”,并且不可配对。 (P5是有序的2宽超标量,其中包含一些可以一起执行指令的配对规则)。对于保护模式而言,这似乎很便宜,所以也许2处于实模式,而保护模式大于?从他的P4表中我们知道,他那时曾以16位模式进行测试。
Agner Fog's microarch guide说,Core2 / Nehalem可以重命名段寄存器(第8.7节,重命名寄存器):
所有整数,浮点数,MMX,XMM,标志和段寄存器都可以重命名。浮点控制字也可以重命名。
(奔腾M不能不能重命名FP控制字,因此更改舍入模式将阻止FP指令的执行。例如,所有较早的FP指令必须先完成,然后才能修改控制字,并且以后的版本要等到以后才能启动。我猜分段规则将是相同的,但是用于加载和存储微指令。)
他说,Sandybridge可以“大概”重命名段reg,而Haswell / Broadwell / Skylake可以“也许”重命名它们。我在SKL上进行的快速测试表明,重复编写相同的段reg慢于编写不同的段reg,这表明它们没有完全重命名。放弃支持似乎是一件显而易见的事情,因为很少会在普通的32/64位代码中对它们进行修改。
每个seg reg通常一次只能修改一次,因此对于同一个段寄存器,运行中的多个dep链不是很有用。 (即,在Linux中,您不会看到段规则的WAW hazards,并且WAR几乎没有关系,因为内核不会在内核入口点中将用户空间的DS用于任何内存引用。(我认为中断是进行序列化,但是通过syscall
进入内核可能仍在用户空间中加载或在飞行中存储但尚未执行。)
在第2章中,一般解释了exec乱序(除P1 / PMMX以外的所有CPU),2.2寄存器重命名表示“可能可以重命名段寄存器”,但是IDK表示可以重命名某些CPU,并且一些不知道,或者他不确定某些旧CPU。他在PII / PII或Pentium-M部分中没有提及seg reg重命名,因此我无法告诉您您显然在询问的仅32位的旧CPU。 (而且他在K8之前没有AMD的微架构指南部分。)
如果您好奇,可以使用性能计数器自己对它进行基准测试。 (有关如何测试以阻止乱序执行的示例,请参见Are loads and stores the only instructions that gets reordered?,有关在Linux上使用perf
在微小循环上进行微基准测试的基础知识,请参见Can x86's MOV really be "free"? Why can't I reproduce this at all?。)>
mov
相对便宜:它仅修改GP寄存器,CPU擅长写入GP寄存器,其中的寄存器为-重命名等。AgnerFog发现这只是Nehalem上的一个小问题。有趣的是,在Core2 / Nehalem上,它运行在加载端口上,所以我想这就是段规则存储在该微体系结构上的地方。
(除了P4:显然,在这里读取seg regs非常昂贵。)
在我的Skylake(长模式)上进行的快速测试显示,mov eax, fs
(或cs
或ds
或其他任何值)为2 oups ,其中之一它只能在端口1上运行,另一个可以在p0156中的任何一个上运行。 (即,它在ALU端口上运行)。每个端口的吞吐量为1,瓶颈是端口1。
通常,您只使用FS或GS进行线程本地存储,而对FS则不使用mov
,而是进行系统调用以使操作系统使用{{1 }},以修改缓存的细分描述中的细分受众群。
N.B我关心的是旧的x86 linux cpus,而不是现代的x86_64 cpus,因为分段的工作原理不同。
您说的是“ Linux”,所以我假设您指的是保护模式,而不是实模式(分段的工作原理完全不同)。 wrfsbase
可能在实模式下的解码方式有所不同,但是我没有一个测试设置,可以在该测试设置中使用性能计数器来分析实模式或本机运行的VM86模式。
在长模式下,FS和GS的工作原理与在保护模式下基本相同,而其他seg reg在长模式下被“抵消”。我认为Agner Fog的Core2 / Nehalem编号可能类似于您在保护模式下的PIII中看到的编号。它们属于同一微体系结构家族。我认为对于保护模式下的P5奔腾段寄存器写入,我们没有有用的数字。
(Sandybridge是从P6-家族派生的新家族中的第一个,内部发生了重大变化,P4的一些想法采用了不同的(更好)方式,例如SnB的解码uop缓存不是 not 但更重要的是,SnB使用物理寄存器文件而不是在ROB中正确保留 values ,因此其寄存器重命名机制有所不同。)
答案 1 :(得分:2)
补充一下 Peter 所说的,寄存器之间的移动只是在使用 Sandy Bridge 的 PRF 方案时将指定架构寄存器的 RAT 指针更改为源架构寄存器的情况,因此没有执行单元.
从微序列器移动到段寄存器大约是 8 uop。它在 nehalem 上也有 14 个周期的倒数吞吐量,这意味着会发生管道刷新并且它可能作为微代码辅助运行。微码例程包含描述符的内存加载到专用描述符寄存器,作为 RS(预订站)中的目的地。
移动到段寄存器可以通过重命名机制处理。段寄存器可以与描述符一起重命名,然后从逻辑地址加载导致描述符被复制到保留站中作为源以及偏移寄存器,并由具有 AGU 的执行端口处理。这可能会造成浪费,因为 RS 必须为每个条目都有一个描述符字段,其中对于每个条目,DS 段将被相同地读取并复制到 RS 中。有英特尔专利对此进行了讨论。有人建议 RS 还可以为段寄存器源或目标以及描述符源或目标具有单独的条目。
或者,移动到段寄存器可以简单地刷新和序列化管道,确保无序内核中的所有内存操作使用正确的段描述符。这必须在远调用中更改 CS 段时发生,因为解码阶段取决于内存和操作数大小的描述符字段。对于 mov,AGU 可以根据操作码字段中的段覆盖直接从段描述符中读取,而不必从 RS 中读取重命名的描述符。远跳实际上可以由 MSROM 在线完成,而不是退出,因为不会对远跳进行预测,并且它总是错误地预测 not-taken,这会导致解码器具有更新的 CS,作为 CS 和 CS 描述符写入在管道重新转向正确的线性地址之前完成。
从段寄存器加载显然不是通过改变 RAT 指针来完成的; uops 实际执行,表明段寄存器和整数寄存器有单独的专用寄存器用于重命名。我猜他们和控制寄存器不能重命名,并且有一个专用寄存器只能重命名源。