gpt4 book ai didi

linux - 在不使用弹出操作的情况下读取数据是否有优势?

转载 作者:太空宇宙 更新时间:2023-11-04 10:01:53 25 4
gpt4 key购买 nike

根据this PDF document (Page-66),下面一组语句

mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]

相当于以下一组语句:
pop eax
pop eax
pop eax

前者比后者有什么优势吗?

最佳答案

mov将数据留在堆栈上,pop将其删除,这样您只能按顺序读取一次数据。除非您使用的调用约定/ABI在堆栈指针下面包含红色区域,否则必须将ESP下面的数据视为“丢失”。
数据通常仍在ESP之下,但是异步的东西,比如信号处理程序,或者在您的进程上下文中计算call fflush(0)的调试器,可以单步执行。
此外,pop还修改ESP,因此每个pop都需要在可执行文件/库的另一部分中使用堆栈展开元数据1,以使其完全符合Windows上的SEH或其他os上的i386/x86-64 System V ABI(它指定所有函数都需要展开元数据,即使它们不是支持传播异常的C++函数。
但是,如果您是最后一次读取数据,而且您实际上需要所有这些数据,那么yes pop是在现代CPU上读取数据的有效方法(例如奔腾M和更高版本,使用a stack engine to handle the ESP updates而不使用单独的uop)
在更老的CPU上,比如奔腾III,pop实际上比3xmov+add esp,12慢,编译器确实按照布伦丹的答案生成了代码。

void foo() {
asm("" ::: "ebx", "esi", "edi");
}

此函数强制编译器保存/还原3个保留调用的寄存器(通过在它们上声明clobber)。它实际上没有触及它们;内联asm字符串为空。但这使得我们很容易看到编译器将如何保存/恢复。(这是他们正常使用 pop的唯一时间。)
GCC的默认(tune=generic)代码生成器,或者例如使用 -march=skylake是这样的( from the Godbolt compiler explorer
foo:                        # gcc8.3 -O3 -m32
push edi
push esi
push ebx
pop ebx
pop esi
pop edi
ret

但是,让它在没有堆栈引擎的情况下为旧的CPU进行优化可以做到这一点:
foo:                     # gcc8.3  -march=pentium3 -O3 -m32
sub esp, 12
mov DWORD PTR [esp], ebx
mov DWORD PTR [esp+4], esi
mov DWORD PTR [esp+8], edi
mov ebx, DWORD PTR [esp]
mov esi, DWORD PTR [esp+4]
mov edi, DWORD PTR [esp+8]
add esp, 12
ret

gcc认为 -march=pentium-m没有堆栈引擎,或者至少选择不在那里使用 push/pop。我认为这是一个错误,因为 Agner Fog's microarch pdf肯定地将堆栈引擎描述为存在于奔腾-M中。
在P-M和更高版本上,push/pop是单uop指令,ESP更新在无序后端之外处理,对于push,存储地址+存储数据uop是微融合的。
在奔腾3上,它们每个都是2或3 UOP。(同样,请参阅Agner Fog的指令表。)
按照P5奔腾的顺序, pushpop实际上都很好。(但是像 add [mem], reg这样的内存目标指令通常是被避免的,因为P5没有将它们拆分成uop以便更好地进行流水线操作。)
在现代英特尔CPU上,混合 pop直接引用 [esp]实际上可能比一个或另一个慢,因为它需要额外的堆栈同步UOP。
显然,背对背地写3次EAX意味着前两次加载在这两个序列中都是无用的。
有关pop(1 uop,或类似于1.1 uop,堆栈同步uop已摊销)比lodsd(Skylake上的2 uop)更有效地读取数组的示例,请参见 Extreme Fibonacci。(在邪恶的代码中,由于没有安装信号处理程序,所以假设有一个很大的红色区域。除非你清楚地知道自己在做什么,什么时候会坏掉,否则不要真的这么做;对于代码高尔夫来说,这更像是一个愚蠢的计算机技巧/极端的优化,而不是任何实际有用的东西。)
脚注1:Godbolt编译器资源管理器通常会过滤掉额外的汇编程序指令,但是如果您取消选中该框,您可以看到gcc使用push/pop的函数在每次push/pop之后都有 .cfi_def_cfa_offset 12
        pop     ebx
.cfi_restore 3
.cfi_def_cfa_offset 12
pop esi
.cfi_restore 6
.cfi_def_cfa_offset 8
pop edi
.cfi_restore 7
.cfi_def_cfa_offset 4

无论push/pop还是mov,都必须有 .cfi_restore 7元数据指令,因为这样可以在堆栈展开时恢复保留的调用寄存器。( 7是寄存器号)。
但是对于函数内部的push/pop的其他用途(比如将参数推送到函数调用,或者使用一个虚拟的 pop将其从堆栈中移除),则不会有 .cfi_restore,只有堆栈指针的元数据相对于堆栈帧更改。
通常情况下,您不必担心手工编写的asm,但编译器必须正确处理这一点,因此就可执行文件的总大小而言,使用 push/pop有一点额外的成本。但只在文件中没有正常映射到内存、没有与代码混合的部分。

关于linux - 在不使用弹出操作的情况下读取数据是否有优势?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55535012/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com