具体是:
mov %eax, %ds
慢于
mov %eax, %ebx
或者他们是相同的速度.我在网上研究过,但一直无法找到明确的答案.
我不确定这是否是一个愚蠢的问题,但我认为修改分段寄存器可以使处理器做额外的工作.
NB我关注旧的x86 linux cpus,而不是现代的x86_64 cpus,其中分段的工作方式不同.
1> Peter Cordes..:
mov %eax, %ebx
通用寄存器之间是最常用的指令之一.现代硬件非常有效地支持它,通常具有不适用于任何其他指令的特殊情况.在较旧的硬件上,它始终是最便宜的指令之一.
在Ivybridge及更高版本中,它甚至不需要执行单元并且具有零延迟.它在寄存器重命名阶段处理. x86的MOV真的可以"免费"吗?为什么我不能重现这个呢? 即使在早期的CPU上,任何ALU端口都是1 uop(因此通常每个时钟吞吐量为3或4).
在AMD Piledriver/Steamroller mov r32,r32
和r64上,r64可以在AGU端口和ALU端口上运行,每个时钟吞吐量为4,而每个时钟有2个用于添加,或者用于mov
8或16位寄存器(必须合并到目的地).
mov
在典型的32位和64位代码中,段reg是一种相当罕见的指令.它是内核为每个系统调用(可能是中断)所做的事情的一部分,因此使其高效将加速系统调用和I/O密集型工作负载的快速路径.因此即使它只出现在少数地方,它也可以运行相当数量.但与之相比,它仍然不太重要mov r,r
!
mov
到段REG慢:它触发从GDT或LDT更新描述符缓冲的负载,所以它的微码.
即使在x86-64长模式下也是如此 ; 在段基/极限字段的GDT项被忽略,但它仍然有更新与其他领域的描述符缓冲段描述符,其中包括不适用于数据段的DPL(描述符特权级).
Agner Fog的指令表列出mov sr, r
了Nehalem和早期CPU的uop计数和吞吐量(Intel synax,mov to segment reg).他不再为以后的CPU测试seg regs,因为它很模糊而且没有被编译器使用(或者人工优化),但SnB系列的数量可能有些相似.( InstLatx64也不测试seg regs,例如不在此 Sandybridge指令定时测试中)
MOV sr,r
在Nehalem上(可能在保护模式或长模式下测试):
前端有6个融合域uops
ALU端口3个uop(p015)
装载端口为3 uops(p2)
吞吐量:每13个周期1个(在巨型循环中重复该指令数千次).IDK,如果CPU重命名段regs.如果没有,它可能会延迟以后的加载(或所有后来的指令?),直到更新描述符缓存并且mov to sr指令退出.即我不确定这会对周围代码的无序执行产生多大影响.
其他CPU类似:
PPro/PII/PIII(原始P6):p0为8 uops,未列出吞吐量.5周期延迟.(请记住,这个uarch是在1995年发布之前设计的,当时16位代码仍然很常见.这就是P6系列为整数寄存器(AL,AH与AX分开)进行部分寄存器重命名的原因)
奔腾4:4 uops + 4微码,14c吞吐量.
延迟= 12c 16位实数或vm86模式,32位保护模式下24c.12c是他在主表中列出的,所以可能他的其他CPU的延迟数也是实模式延迟,其中写一个段reg只是设置base = sreg<<4
.)
与其他CPU不同,在P4上读取段reg很慢:4 uops + 4个微码,6c吞吐量
P4 Prescott:1 uop + 8微码.27c吞吐量. 读取段reg = 8c吞吐量.
Pentium M:p0为8 uops,与PIII相同.
Conroe/Merom和Wolfdale/Penryn(第一代和第二代Core2):8个融合域uop,4个ALU(p015),4个负载/ AGU(p2).每16个周期吞吐量一个,是Agner测试它的任何CPU中最慢的.
Skylake(我的测试用我在循环外读取的值重新加载它们):在一个只有dec/jnz的循环中:10个融合域uops(前端),6个unfused-domain(执行单元).每18c吞吐量一个.
在写入4个不同 seg regs(ds/es/fs/gs)的循环中,都使用相同的选择器:mov
每25c吞吐量4 个,6个融合/未融合域uop.(也许有些人被取消了?)
在写入ds
4次循环中:每72c一次(mov ds,eax
每18c一次).相同的uop计数:~6个融合和未融合mov
.
这似乎表明Skylake 没有重命名段regs:写入一个必须在下一次写入开始之前完成.
K7/K8/K10:6"操作",8c吞吐量.
原子:7 uops,21c吞吐量
通过Nano 2000/3000:未列出的uops,20个周期的吞吐量和延迟.Nano 3000具有0.5个循环吞吐量,用于读取 seg reg(mov r, sr
).没有列出延迟,这很奇怪.也许他正在测量seg-write延迟,以便何时可以将它用于负载?喜欢mov eax, [ebx]
/ mov ds, eax
在循环中?
奇怪的Al是对的,它是关于Pentiums的全部
按顺序Pentium(P5/PMMX)有更便宜的mov-to-sr:Agner将其列为"> = 2个循环",并且不可配对.(P5是有序的2-wide superscalar,有一些配对规则,指令可以一起执行).这似乎对保护模式来说很便宜,所以2可能处于实模式,而保护模式可能大于?我们从他的P4表中了解到他当时以16位模式测试过.
Agner Fog的微型指南说Core2/Nehalem可以重命名段寄存器(第8.7节注册重命名):
可以重命名所有整数,浮点,MMX,XMM,标志和段寄存器.浮点控制字也可以重命名.
(Pentium M 无法重命名FP控制字,因此更改舍入模式会阻止FP指令的OoO执行.例如,所有早期的FP指令必须先完成才能修改控制字,后来才能在之后启动.I猜段段regs是相同的,但对于加载和存储uops.)
他说Sandybridge可能"可能"重命名段regs,Haswell/Broadwell/Skylake可以"或许"重命名它们.我对SKL的快速测试表明,重复编写相同的段reg比写入不同的段reg更慢,这表明它们没有完全重命名.删除支持似乎是一件显而易见的事情,因为它们在普通的32/64位代码中很少被修改.
并且每个seg reg通常一次只修改一次,因此对于相同的段寄存器而言,多个dep链在飞行中并不是非常有用.(即,你不会在Linux中看到针对段注册的WAW危险,而WAR几乎不相关,因为内核不会在内核入口点使用用户空间的DS进行任何内存引用.(我认为中断是序列化的,但是通过进入内核syscall
可能仍然有用户空间加载或存储在飞行中但尚未执行.)
在第2章中,它解释了无序执行程序(除了P1/PMMX之外的所有CPU),2.2寄存器重命名表示"可能会重命名段寄存器",但IDK如果他意味着某些CPU可以执行,而某些CPU则需要重命名t,或者如果他不确定一些旧的CPU.他没有提到PII/PII或Pentium-M部分中的seg reg重命名,所以我不能告诉你关于你显然要问的旧32位CPU.(而且他在K8之前没有针对AMD的微型指南部分.)
如果您对性能计数器感到好奇,您可以自己进行基准测试.(请参阅是否加载并存储重新排序的唯一指令?有关如何测试阻止无序执行的示例,并且x86的MOV真的可以"免费"吗?为什么我不能重现这个?)了解perf
在Linux 上使用微小循环上的微基准测试的基础知识.
阅读段注册
mov
来自段寄存器相对便宜:它只修改了一个GP寄存器,并且CPU擅长写入GP寄存器,使用寄存器重命名等.Agner Fog发现它在Nehalem上只是一个uop.有趣的是,在Core2/Nehalem上它运行在加载端口上,所以我猜这就是段regs存储在该微体系结构上的地方.
(除了P4:显然在那里阅读seg regs是昂贵的.)
对我的Skylake进行快速测试(在长模式下)显示mov eax, fs
(或者cs
或者ds
其他)是2 uops,其中一个只在端口1上运行,另一个可以在p0156上运行.(即它在ALU端口上运行).它的吞吐量为每时钟1个,在端口1上有瓶颈.
您通常只使用FS或GS进行线程本地存储,而不是使用mov
FS,您进行系统调用以使用OS wrfsbase
来修改缓存段描述中的段基.
NB我关注旧的x86 linux cpus,而不是现代的x86_64 cpus,其中分段的工作方式不同.
你说"Linux",所以我假设你的意思是保护模式,而不是真实模式(分割的工作方式完全不同).mov sr, r
在实模式下可能以不同的方式进行解码,但我没有测试设置,我可以使用性能计数器进行配置文件,以实现本机运行的真实或VM86模式.
长模式下的FS和GS基本上与保护模式下的相同,它是在长模式下"绝育"的其他seg reg.我认为Agner Fog的Core2/Nehalem数字可能类似于您在保护模式下的PIII中看到的数字.他们是同一个微体系结构家族的一部分.我认为在保护模式下P5奔腾段寄存器写入没有一个有用的数字.
(Sandybridge是源自P6系列的新系列中的第一个,具有显着的内部变化,而P4的一些想法实现了不同的(更好的)方式,例如SnB的解码uop缓存不是跟踪缓存.但更重要的是,SnB使用一个物理寄存器文件,而不是在ROB 中保持值,所以它的寄存器重命名机制是不同的.)