我使用英特尔®架构代码分析器(IACA)发现了一些意想不到的东西(对我而言).
以下指令使用[base+index]寻址
addps xmm1, xmmword ptr [rsi+rax*1]
Run Code Online (Sandbox Code Playgroud)
根据IACA没有微熔丝.但是,如果我用[base+offset]这样的
addps xmm1, xmmword ptr [rsi]
Run Code Online (Sandbox Code Playgroud)
IACA报告它确实融合了.
英特尔优化参考手册的第2-11节给出了以下"可以由所有解码器处理的微融合微操作"的示例
FADD DOUBLE PTR [RDI + RSI*8]
Run Code Online (Sandbox Code Playgroud)
和Agner Fog的优化装配手册也给出了使用[base+index]寻址的微操作融合的例子.例如,请参见第12.2节"Core2上的相同示例".那么正确的答案是什么?
来自Ira Baxter回答,为什么INC和DEC指令不会影响进位标志(CF)?
大多数情况下,我远离
INC而DEC现在,因为他们做的部分条件代码更新,这样就可以在管道中引起滑稽的摊位,和ADD/SUB没有.因此,无关紧要(大多数地方),我使用ADD/SUB避免失速.我使用INC/DEC仅在保持代码较小的情况下,例如,适合高速缓存行,其中一个或两个指令的大小产生足够的差异.这可能是毫无意义的纳米[字面意思!] - 优化,但我在编码习惯上相当老派.
我想问一下为什么它会导致管道中的停顿,而添加不会?毕竟,无论是ADD和INC更新标志寄存器.唯一的区别是INC不更新CF.但为什么重要呢?
我被告知并且从英特尔的手册中读到可以将指令写入内存,但是指令预取队列已经获取了陈旧的指令并将执行那些旧的指令.我没有成功观察到这种行为.我的方法如下.
英特尔软件开发手册从第11.6节开始说明
对当前在处理器中高速缓存的代码段中的存储器位置的写入导致相关联的高速缓存行(或多个行)无效.此检查基于指令的物理地址.此外,P6系列和奔腾处理器检查对代码段的写入是否可以修改已经预取执行的指令.如果写入影响预取指令,则预取队列无效.后一种检查基于指令的线性地址.
所以,看起来如果我希望执行陈旧的指令,我需要有两个不同的线性地址引用相同的物理页面.所以,我将内存映射到两个不同的地址.
int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
assert(fd>=0);
write(fd, zeros, 0x1000);
uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
assert(a1 != a2);
Run Code Online (Sandbox Code Playgroud)
我有一个汇编函数,它接受一个参数,一个指向我想要更改的指令的指针.
fun:
push %rbp
mov %rsp, %rbp
xorq %rax, %rax # Return value 0
# A far jump simulated with a far return
# Push the …Run Code Online (Sandbox Code Playgroud) 我想知道各种大小的循环如何在最近的x86处理器上执行,作为uop数的函数.
以下是彼得·科德斯(Peter Cordes)的一句话,他在另一个问题中提出了非多数的问题:
我还发现,如果循环不是4 uop的倍数,则循环缓冲区中的uop带宽不是每个循环的常数4.(即它是abc,abc,......;不是abca,bcab,......).遗憾的是,Agner Fog的microarch doc对循环缓冲区的这种限制并不清楚.
问题是关于循环是否需要是N uop的倍数才能以最大uop吞吐量执行,其中N是处理器的宽度.(即最近的英特尔处理器为4).在谈论"宽度"和计算微动时,有很多复杂因素,但我大多想忽略这些因素.特别是,假设没有微观或宏观融合.
Peter给出了以下一个循环,其中包含7个uop的循环:
一个7-uop循环将发出4 | 3 | 4 | 3 | ...的组我没有测试更大的循环(不适合循环缓冲区),看看是否有可能从下一个指令开始迭代发布在与其分支相同的组中,但我不假设.
更一般地说,声称是x在其体内具有uops 的循环的每次迭代将至少进行ceil(x / 4)迭代,而不是简单地迭代x / 4.
对于部分或全部最新的x86兼容处理器,这是真的吗?
performance x86 assembly cpu-architecture micro-optimization
在 x86-64 中,如果某些通用寄存器比其他寄存器更受欢迎,某些指令会执行得更快吗?
例如,mov eax, ecx执行速度会比mov r8d, ecx? 我可以想象后者需要一个 REX 前缀,这会使指令获取速度变慢?
使用rax而不是怎么样rcx?怎么样add还是xor?其他操作?较小的寄存器,如r15bvs al? al与ah?
AMD VS 英特尔?较新的处理器?旧处理器?指令组合?
澄清:某些通用寄存器是否应该优先于其他寄存器,它们是哪些?
我想用英特尔I64汇编程序做一些长整数数学运算(128位),需要创建一个2的补码.让我们说我的正面价值在于RDX:RAX.
2的补码是通过"翻转位并加1"来完成的.所以最天真的实现是(4条指令和14个字节的代码):
NOT RAX
NOT RDX
ADD RAX,1 ; Can't use INC, it doesn't set Carry
ADC RDX,0
Run Code Online (Sandbox Code Playgroud)
当我在RAX而不是NOT上使用NEG指令时,它对我来说是"+1"但是Carry是错误的,当RAX为零时NEG RAX清除了Carry,但是我需要携带JUST IN THIS CASE.所以下一个最好的方法可能是(4条指令和11个字节的代码):
NOT RDX
NEG RAX
CMC
ADC RDX,0 ; fixed, thanks lurker
Run Code Online (Sandbox Code Playgroud)
还有4条说明.但是不是加+1,我可以减去-1,因为SBB将Carry-Bit加到减数上,当Carry清零时我会加+1.所以我的下一个最好的尝试是这个,有3个指令和10个字节的代码:
NOT RDX
NEG RAX
SBB RDX,-1
Run Code Online (Sandbox Code Playgroud)
从我冗长的文字中可以看出,这一点并不明显.是否有一种更好,更易理解的方法来在汇编程序中进行级联2的补码?
如果您正在编写程序集,那么将值分配给哪个寄存器重要吗?假设您将累积/中间值存储在 %ebx 而不是 %eax 中,后者传统上用于此目的。这是不好的做法吗?会影响性能吗?
换句话说,您是否可以将它们等同于存储空间,还是应该坚持将它们用于特定目的?
我正在尝试针对特定的 Kaby Lake CPU (i5-7300HQ) 优化以下子例程,理想情况下,与原始形式相比,代码速度至少快 10 倍。该代码在 16 位实模式下作为软盘式引导加载程序运行。它在屏幕上显示一个十位数的十进制计数器,从 0 - 9999999999 计数然后停止。
我查看了 Agner 的微体系结构和汇编优化指南、 指令性能表和英特尔的优化参考手册。
到目前为止,我能够做的唯一明智的优化是将loop指令交换为dec + jnz,在此处进行解释。
另一种可能的优化可能是交换lodsbfor mov + dec,但我发现的关于它的信息一直存在冲突,有些人说它有一点帮助,而另一些人则认为它实际上可能会损害现代 CPU 的性能。
我还尝试切换到 32 位模式并将整个计数器保留在一个未使用的寄存器对中以消除任何内存访问,但在读入一点后我意识到这十位将立即被缓存,并且 L1 缓存之间的延迟差异和寄存器只有大约三倍,所以绝对不值得以这种格式使用计数器的额外开销。
(编者注:add reg延迟为 1 个周期,add [mem]延迟约为 6 个周期,包括 5 个周期的存储转发延迟。如果[mem]像视频 RAM 那样不可缓存,则更糟。)
org 7c00h
pos equ 2*(2*80-2) ;address on screen
;init
cli
mov ax,3
int 10h
mov …Run Code Online (Sandbox Code Playgroud) 例如,假设一个变量x可以
x是任何 include 0。
然后我们得到如下代码:
if(x==0) {
y = 1;
}
else {
y = x;
}
Run Code Online (Sandbox Code Playgroud)
我可以在不使用 C/C++ 生成分支的情况下执行此操作吗?
我正在尝试优化一段代码。我想尽可能地去除树枝。还有类似的判断,所以我想把它们转换成没有分支的语句,让代码尽可能高效。
我有一个32位数字,想知道有多少位是1.
我在考虑这个伪代码:
mov eax, [number]
while(eax != 0)
{
div eax, 2
if(edx == 1)
{
ecx++;
}
shr eax, 1
}
Run Code Online (Sandbox Code Playgroud)
有更有效的方法吗?
我在x86处理器上使用NASM.
(我刚开始使用汇编程序,所以请不要告诉我使用extern库中的代码,因为我甚至不知道如何包含它们;))
(我刚刚发现如何计算32位整数中的设置位数?这也包含我的解决方案.还有其他解决方案,但不幸的是我似乎无法弄清楚,我将如何在汇编程序中编写它们)
考虑以下 x86 程序集:
; something that sets rax
mov rcx, [rdi]
xor rax, rcx
xor rax, rcx
Run Code Online (Sandbox Code Playgroud)
在序列末尾,rax的值与进入时的值相同,但从 CPU 的角度来看,它的值取决于从内存加载到 的值rcx。特别是,rax在该加载和两条指令完成之前,后续的使用不会开始xor。
有没有什么方法可以比双xor序列更有效地实现这种效果,例如,使用单个单微指令单周期延迟指令?如果某个常量值需要在序列之前设置一次(例如,有一个归零的寄存器),这是可以的。
我正在编写自己的汇编程序并尝试对 ADC 指令进行编码,我对立即值有疑问,尤其是在将 8 位值添加到 AX 寄存器时。
添加 16 位值时:adc ax, 0xff33被编码为15 33 ff正确的。但是如果adc ax, 0x33被编码为会重要15 33 00吗?
Nasm 将83 d0 33其编码为显然是正确的,但我的方法也正确吗?
像https://uops.info/和 Agner Fog 的指令表,甚至英特尔自己的手册这样的网站,都列出了相同指令的各种形式。例如add m, r(在 Agner 的表格中)或add (m64, r64)在 uops.info 上,或ADD r/m64, r64在英特尔的手册中(https://www.felixcloutier.com/x86/add)。
这是我在 Godbolt 上运行的一个简单示例
__thread int a;
void Test() {
a+=5;
}
Run Code Online (Sandbox Code Playgroud)
添加是add DWORD PTR fs:0xfffffffffffffffc,0x5。它以操作码开头64 83 04 25。
有几种方法可以编写我的真实代码,但我想查找这可能需要多少个周期以及其他信息。我怎么找到这个指令的参考?我尝试在https://uops.info/table.html 中输入“add”并检查我的架构。但我不知道哪个条目是正在使用的指令。
现在在这种特定情况下,我猜测操作码是Add m64, r64但我不知道fs:在地址之前使用是否有任何惩罚,或者是否有办法查看操作码,以便我可以确认我正在查看正确的参考
assembly ×11
x86 ×10
performance ×4
x86-64 ×3
c ×2
intel ×2
machine-code ×2
accumulator ×1
bootloader ×1
caching ×1
cpu ×1
disassembly ×1
iaca ×1
increment ×1
nasm ×1
opcode ×1
optimization ×1