Jan*_*nis 17 c++ assembly gcc compiler-optimization
在逐步执行一些Qt代码时,我遇到了以下内容.该函数QMainWindowLayout::invalidate()具有以下实现:
void QMainWindowLayout::invalidate()
{
QLayout::invalidate()
minSize = szHint = QSize();
}
Run Code Online (Sandbox Code Playgroud)
它被编译为:
<invalidate()> push %rbx
<invalidate()+1> mov %rdi,%rbx
<invalidate()+4> callq 0x7ffff4fd9090 <QLayout::invalidate()>
<invalidate()+9> movl $0xffffffff,0x564(%rbx)
<invalidate()+19> movl $0xffffffff,0x568(%rbx)
<invalidate()+29> mov 0x564(%rbx),%rax
<invalidate()+36> mov %rax,0x56c(%rbx)
<invalidate()+43> pop %rbx
<invalidate()+44> retq
Run Code Online (Sandbox Code Playgroud)
从invalidate + 9到invalidate + 36的程序集似乎很愚蠢.首先,代码将-1写入%rbx + 0x564和%rbx + 0x568,但是然后它将-1从%rbx + 0x564加载回寄存器,只是将其写入%rbx + 0x56c.这似乎是编译器应该能够轻松优化到另一个立即行动的东西.
那么这个愚蠢的代码(如果是这样,为什么编译器不会对它进行优化?)或者这是否比使用另一个立即动作更聪明,更快?
(注意:此代码来自ubuntu提供的正常发布库版本,因此它可能是由GCC在优化模式下编译的.minSize而且szHint变量是类型的正常变量QSize.)
Gui*_*ume 12
当你说这是愚蠢的时候,不确定你是否正确.我认为编译器可能会尝试在此处优化代码大小.没有64位立即到内存的mov指令.所以编译器必须像上面那样生成2个mov指令.它们中的每一个都是10个字节,生成的2个移动是14个字节.它已写入,所以很可能没有内存延迟,所以我认为你不会在这里受到任何性能影响.
代码"不完美".
对于代码大小,这4条指令最多可添加34个字节.可以使用更小的序列(19个字节):
00000000 31C0 xor eax,eax
00000002 48F7D0 not rax
00000005 48898364050000 mov [rbx+0x564],rax
0000000C 4889836C050000 mov [rbx+0x56c],rax
;Note: XOR above clears RAX due to zero extension
Run Code Online (Sandbox Code Playgroud)
对于表现来说,事情并非如此简单.CPU希望同时执行许多指令,并且上面的代码打破了这一点.例如:
xor eax,eax
not rax ;Must wait until previous instruction finishes
mov [rbx+0x564],rax ;Must wait until previous instruction finishes
mov [rbx+0x56c],rax ;Must wait until "not" finishes
Run Code Online (Sandbox Code Playgroud)
对于性能,您希望这样做:
00000000 48C7C0FFFFFFFF mov rax,0xffffffff
00000007 C78364050000FFFFFFFF mov dword [rbx+0x564],0xffffffff
00000011 C78368050000FFFFFFFF mov dword [rbx+0x568],0xffffffff
0000001B C7836C050000FFFFFFFF mov dword [rbx+0x56c],0xffffffff
00000025 C78370050000FFFFFFFF mov dword [rbx+0x570],0xffffffff
;Note: first MOV sets RAX to 0xFFFFFFFFFFFFFFFF due to sign extension
Run Code Online (Sandbox Code Playgroud)
这允许所有指令并行执行,在任何地方都没有依赖性.可悲的是,它也更大(45字节).
如果您尝试在代码大小和性能之间取得平衡; 那么你可能希望第一条指令(在RAX中设置值)在最后一条指令需要知道RAX中的值之前完成.这可能是这样的:
mov rax,-1
mov dword [rbx+0x564],0xffffffff
mov dword [rbx+0x568],0xffffffff
mov dword [rbx+0x56c],rax
Run Code Online (Sandbox Code Playgroud)
这是34个字节(与原始代码相同).这可能是代码大小和性能之间的良好折衷.
现在; 让我们看看原始代码,看看它为什么是坏的:
mov dword [rbx+0x564],0xffffffff
mov dword [rbx+0x568],0xffffffff
mov rax,[rbx+0x564] ;Massive problem
mov [rbx+0x56C],rax ;Depends on previous instruction
Run Code Online (Sandbox Code Playgroud)
现代CPU确实有一种称为"存储转发"的东西,其中写入存储在缓冲区中,将来的读取可以从此缓冲区获取值以避免从缓存中读取值.具有讽刺意味的是,只有当读取的大小小于或等于写入的大小时,这才有效."存储转发"对此代码不起作用,因为有2次写入,并且读取大于它们.这意味着第三条指令必须等到前2条指令写入高速缓存然后必须从高速缓存中读取该值; 这可能很容易加起来约30个周期或更多的惩罚.然后第四条指令必须等待第三条指令(并且不能与任何东西并行发生),这是另一个问题.