我使用英特尔®架构代码分析器(IACA)发现了一些(对我来说)意想不到的东西。
以下使用[base+index]
寻址的指令
addps xmm1, xmmword ptr [rsi+rax*1]
不符合IACA规定的微型保险丝。但是,如果我这样使用[base+offset]
addps xmm1, xmmword ptr [rsi]
FADD DOUBLE PTR [RDI + RSI*8]
在解码器和uop-cache中,寻址模式不影响微融合(除了具有立即操作数的指令不能微融合RIP相关寻址模式)。
但是uop和寻址模式的某些组合不能在ROB中保持微融合(在无序的内核中),因此Intel SNB系列CPU在必要时在发布/重命名阶段之前的某个点“取消层压”。对于问题吞吐量和无序窗口大小(ROB-size)来说,解层后的融合域uop计数是重要的。
Intel的优化手册在第2.5.2.4节:微操作队列和循环流检测器(LSD)中描述了Sandybridge的去层压,但没有描述任何以后的微架构的变化。
另请参阅Agner Fog博客上的这篇文章,以了解当您读取大量寄存器时HSW/SKL的问题吞吐量限制:大量使用索引寻址模式的微融合可能导致速度减慢,而与使用较少寄存器操作数的相同指令相比:单寄存器寻址模式和即时寻址模式。我们还不知道原因,但我怀疑某种寄存器读取限制,可能与从PRF读取大量冷寄存器有关。
测试用例,来自真实测量的数字:这些都是微融合在解码器,AFAIK,即使他们后来不层压。
# store
mov [rax], edi SnB/HSW/SKL: 1 fused-domain, 2 unfused. The store-address uop can run on port7.
mov [rax+rsi], edi SnB: unlaminated. HSW/SKL: stays micro-fused. (The store-address can't use port7, though).
mov [buf +rax*4], edi SnB: unlaminated. HSW/SKL: stays micro-fused.
# normal ALU stuff
add edx, [rsp+rsi] SnB: unlaminated. HSW/SKL: stays micro-fused.
# I assume the majority of traditional/normal ALU insns are like add
HSW/SKL可能必须取消层压的三输入指令
vfmadd213ps xmm0,xmm0,[rel buf] HSW/SKL: stays micro-fused: 1 fused, 2 unfused.
vfmadd213ps xmm0,xmm0,[rdi] HSW/SKL: stays micro-fused
vfmadd213ps xmm0,xmm0,[0+rdi*4] HSW/SKL: un-laminated: 2 uops in fused & unfused-domains.
(So indexed addressing mode is still the condition for HSW/SKL, same as documented by Intel for SnB)
# no idea why this one-source BMI2 instruction is unlaminated
# It's different from ADD in that its destination is write-only (and it uses a VEX encoding)
blsi edi, [rdi] HSW/SKL: 1 fused-domain, 2 unfused.
blsi edi, [rdi+rsi] HSW/SKL: 2 fused & unfused-domain.
adc eax, [rdi] same as cmov r, [rdi]
cmove ebx, [rdi] Stays micro-fused. (SnB?)/HSW: 2 fused-domain, 3 unfused domain.
SKL: 1 fused-domain, 2 unfused.
# I haven't confirmed that this micro-fuses in the decoders, but I'm assuming it does since a one-register addressing mode does.
adc eax, [rdi+rsi] same as cmov r, [rdi+rsi]
cmove ebx, [rdi+rax] SnB: untested, probably 3 fused&unfused-domain.
HSW: un-laminated to 3 fused&unfused-domain.
SKL: un-laminated to 2 fused&unfused-domain.
我假设Broadwell的行为与ADC/CMOV的Skylake类似。
add [rdi], eax SnB: untested (Agner says 2 fused-domain, 4 unfused-domain (load + ALU + store-address + store-data)
HSW/SKL: 2 fused-domain, 4 unfused.
add [rdi+rsi], eax SnB: untested, probably 4 fused & unfused-domain
HSW/SKL: 3 fused-domain, 4 unfused. (I don't know which uop stays fused).
HSW: About 0.95 cycles extra store-forwarding latency vs. [rdi] for the same address used repeatedly. (6.98c per iter, up from 6.04c for [rdi])
SKL: 0.02c extra latency (5.45c per iter, up from 5.43c for [rdi]), again in a tiny loop with dec ecx/jnz
adc [rdi], eax SnB: untested
HSW: 4 fused-domain, 6 unfused-domain. (same-address throughput 7.23c with dec, 7.19c with sub ecx,1)
SKL: 4 fused-domain, 6 unfused-domain. (same-address throughput ~5.25c with dec, 5.28c with sub)
adc [rdi+rsi], eax SnB: untested
HSW: 5 fused-domain, 6 unfused-domain. (same-address throughput = 7.03c)
SKL: 5 fused-domain, 6 unfused-domain. (same-address throughput = ~5.4c with sub ecx,1 for the loop branch, or 5.23c with dec ecx for the loop branch.)
据推测,在解码器中的融合和随后的解层,使我们无需使用微码ROM,即可从ADC[BASE+IDX],reg
的单个指令生成4个以上的融合域UOP。
为什么SNB系列不层压板:
Sandybridge简化了内部uop格式,以节省功率和晶体管(同时还对使用物理寄存器文件进行了重大更改,而不是将输入/输出数据保留在ROB中)。SNB系列CPU只允许为乱序内核中的融合域uop提供有限数量的输入寄存器。对于SNB/IVB,该限制为2个输入(包括标志)。对于HSW及更高版本,一个UOP的限制为3个输入。我不确定memory-destination的Add
和ADC
是否充分利用了这一点,或者Intel是否必须用一些指令来让Haswell退出大门
在i5-4210U和I7-6700K上测量HSW/SKL数。两者都启用了HT(但系统空闲,因此线程拥有整个核心)。我使用ocperf.py
在两个系统上运行了相同的静态二进制文件,SKL上的Linux 4.10和HSW上的Linux 4.8。(HSW笔记本NFS安装的我的SKL桌面/家庭。)
SnB数的测量如下所述,在i5-2500k上,该i5-2500k已不再工作。
通过UOP和循环的性能计数器测试确认。
GLOBAL _start
_start:
xor eax, eax
xor ebx, ebx
xor edx, edx
xor edi, edi
lea rsi, [rel mydata] ; load pointer
mov ecx, 10000000
cmp dword [rsp], 2 ; argc >= 2
jge .loop_2reg
ALIGN 32
.loop_1reg:
or eax, [rsi + 0]
or ebx, [rsi + 4]
dec ecx
nop
nop
nop
nop
jg .loop_1reg
; xchg r8, r9 ; no effect on flags; decided to use NOPs instead
jmp .out
ALIGN 32
.loop_2reg:
or eax, [rsi + 0 + rdi]
or ebx, [rsi + 4 + rdi]
dec ecx
nop
nop
nop
nop
jg .loop_2reg
.out:
xor edi, edi
mov eax, 231 ; exit(0)
syscall
SECTION .rodata
mydata:
db 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
在循环的另一个版本中,使用不微融合的2操作数寻址模式,循环将是10个融合域UOP,并在3个周期中运行。
来自3.3GHz Intel Sandybridge(i5 2500K)的结果。在测试之前,我没有做任何事情来让cpufreq调控器提高时钟速度,因为当您不与内存交互时,周期就是周期。我已经为我必须以十六进制输入的性能计数器事件添加了注释。
测试1-reg寻址模式:无cmdline参数
$ perf stat -e task-clock,cycles,instructions,r1b1,r10e,r2c2,r1c2,stalled-cycles-frontend,stalled-cycles-backend ./uop-test
Performance counter stats for './uop-test':
11.489620 task-clock (msec) # 0.961 CPUs utilized
20,288,530 cycles # 1.766 GHz
80,082,993 instructions # 3.95 insns per cycle
# 0.00 stalled cycles per insn
60,190,182 r1b1 ; UOPS_DISPATCHED: (unfused-domain. 1->umask 02 -> uops sent to execution ports from this thread)
80,203,853 r10e ; UOPS_ISSUED: fused-domain
80,118,315 r2c2 ; UOPS_RETIRED: retirement slots used (fused-domain)
100,136,097 r1c2 ; UOPS_RETIRED: ALL (unfused-domain)
220,440 stalled-cycles-frontend # 1.09% frontend cycles idle
193,887 stalled-cycles-backend # 0.96% backend cycles idle
0.011949917 seconds time elapsed
$ perf stat -e task-clock,cycles,instructions,r1b1,r10e,r2c2,r1c2,stalled-cycles-frontend,stalled-cycles-backend ./uop-test x
Performance counter stats for './uop-test x':
18.756134 task-clock (msec) # 0.981 CPUs utilized
30,377,306 cycles # 1.620 GHz
80,105,553 instructions # 2.64 insns per cycle
# 0.01 stalled cycles per insn
60,218,693 r1b1 ; UOPS_DISPATCHED: (unfused-domain. 1->umask 02 -> uops sent to execution ports from this thread)
100,224,654 r10e ; UOPS_ISSUED: fused-domain
100,148,591 r2c2 ; UOPS_RETIRED: retirement slots used (fused-domain)
100,172,151 r1c2 ; UOPS_RETIRED: ALL (unfused-domain)
307,712 stalled-cycles-frontend # 1.01% frontend cycles idle
1,100,168 stalled-cycles-backend # 3.62% backend cycles idle
0.019114911 seconds time elapsed
不过,如果在循环中进行大量内存引用的代码可以通过适度的展开和递增多个指针来实现,这些指针与简单的[base+immediatedoffset]
寻址模式一起使用,而不是使用[base+index]
寻址模式一起使用,那么在循环中进行大量内存引用的代码可能会更快。
RIP-relative与一个立即不能微型保险丝。Agner Fog的测试表明,即使在解码器/uop-cache中也是如此,因此它们从一开始就不会融合(而不是不层压)。
cmp dword [abs mydata], 0x1b ; fused counters != unfused counters (micro-fusion happened, and wasn't un-laminated). Uses 2 entries in the uop-cache, according to Agner Fog's testing
cmp dword [rel mydata], 0x1b ; fused counters ~= unfused counters (micro-fusion didn't happen)
or eax, dword [rel mydata] ; fused counters != unfused counters, i.e. micro-fusion happens
ALIGN 32
.dep_fuse:
or eax, [rsi + 0]
or eax, [rsi + 0]
or eax, [rsi + 0]
or eax, [rsi + 0]
or eax, [rsi + 0]
dec ecx
jg .dep_fuse
由于eax
dep链,此循环每迭代运行5个循环。不比或eax,[rsi+0+rdi]
或mov ebx,[rsi+0+rdi]/或eax,ebx
的序列快。(未使用的版本和mov
版本都运行相同数量的UOP。)调度/dep检查发生在未使用的域中。新发布的UOP进入调度程序(又名Reservation Station(RS))和ROB。它们在调度后离开调度器(也称为被发送到执行单元),但留在ROB中直到退休。因此隐藏负载延迟的无序窗口至少是调度器大小(Sandybridge中的54个未使用域UOP,Haswell中的60个,Skylake中的97个)。
我对寻址模式有点困惑。 如果我有这样的陈述: EBX是基础,EDI是索引,10是说向EDI再添加10个字节?那么,EDI将返回14字节长?然后返回地址并将其放入EAX? 类似地, 这是说在数组中的任何索引处,返回4个字节长吗?因为数组对每个索引都有4个字节长,对吗?我想我不明白*4是做什么的! 另外, 这将把数字100放入数组,从索引0开始,4字节大。所以,index[0] = 100,index
大多数汇编语言指令都需要处理操作数。 操作数地址提供存储要处理的数据的位置。 某些指令不需要操作数,而某些其他指令可能需要一个,两个或三个操作数。 当指令需要两个操作数时,第一个操作数通常是目标,它包含寄存器或存储单元中的数据,第二个操作数是源。 源包含要传递的数据(立即寻址)或数据的地址(寄存器或存储器)。 通常,源数据在操作后保持不变。 三种基本寻址方式是 - 寄存器寻址 立即寻址 内存寻址
3.6 基址加变址寻址方式 操作数在存储器中,其有效地址是一个基址寄存器(BX、BP)和一个变址寄存器(SI、DI)的内容之和。其有效地址的计算公式如右式所示。 在不使用段超越前缀的情况下,规定:如果有效地址中含有BP,则缺省的段寄存器为SS;否则,缺省的段寄存器为DS。 例3.4 假设指令:MOVBX, [BX+SI],在执行时,(DS)=1000H,(BX)=2100H,(SI)=0011H,
周五晚上,绝影和往常一样回家。这是他和他们这些大学生唯一不同的地方:他家和大学在一个城市,每周五就可以回家。这样做的好处有两点:一、每周只有五天要用生活费;二、可以不用自己洗衣服,冬天的衣服,直接穿回家换就好了,夏天的,打包回家换。 这周过来,他和平时有点不一样,土匪见他提了两本书,一本《鲁迅杂文全集》,一本《PC汇编语言程序设计》。 土匪对这两本书没都没啥兴趣,他喜欢看武打小说,是“武打”小说,
4. 寻址方式 通过上一节的例子我们了解到,访问内存时在指令中可以用多种方式表示内存地址,比如可以用数组基地址、元素长度和下标三个量来表示,增加了寻址的灵活性。本节介绍x86常用的几种寻址方式(Addressing Mode)。内存寻址在指令中可以表示成如下的通用格式: ADDRESS_OR_OFFSET(%BASE_OR_OFFSET,%INDEX,MULTIPLIER) 它所表示的地址可以这样
TCP/IP 使用 32 个比特或者 4 组 0 到 255 之间的数字来为计算机编址。 IP地址 每个计算机必须有一个 IP 地址才能够连入因特网。 每个 IP 包必须有一个地址才能够发送到另一台计算机。 在本教程下一节,您会学习到更多关于 IP 地址和 IP 名称的知识。 IP 地址包含 4 组数字: TCP/IP 使用 4 组数字来为计算机编址。每个计算机必须有一个唯一的 4 组数字的地址。