Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/65.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 我们代码中的函数会使它变慢吗?_C_Assembly_Language Agnostic_X86_Pipeline - Fatal编程技术网

C 我们代码中的函数会使它变慢吗?

C 我们代码中的函数会使它变慢吗?,c,assembly,language-agnostic,x86,pipeline,C,Assembly,Language Agnostic,X86,Pipeline,当我们编译代码并在汇编中执行它时,我们的代码在汇编中转换,函数以非顺序的方式存储。因此,每次调用函数时,处理器都需要丢弃管道中的指令。这不会影响程序的性能吗 附言:我不考虑在没有功能的情况下开发这样的程序所花费的时间。纯粹在性能级别上。编译器有没有办法处理这个问题来减少它 因此,每次调用函数时,处理器都需要丢弃管道中的指令 不,解码阶段后的一切都还不错。CPU知道在无条件分支(如jmp、call或ret)之后不保持解码。只有已获取但尚未解码的指令才是不应运行的指令。在从指令中解码目标地址之前,管

当我们编译代码并在汇编中执行它时,我们的代码在汇编中转换,函数以非顺序的方式存储。因此,每次调用函数时,处理器都需要丢弃管道中的指令。这不会影响程序的性能吗

附言:我不考虑在没有功能的情况下开发这样的程序所花费的时间。纯粹在性能级别上。编译器有没有办法处理这个问题来减少它

因此,每次调用函数时,处理器都需要丢弃管道中的指令

不,解码阶段后的一切都还不错。CPU知道在无条件分支(如
jmp
call
ret
)之后不保持解码。只有已获取但尚未解码的指令才是不应运行的指令。在从指令中解码目标地址之前,管道的开头没有任何有用的操作,所以在知道目标地址之前,管道中会出现气泡。尽可能早地解码分支指令,从而最大限度地减少对执行分支的惩罚

在中,阶段是
IF ID EX MEM WB
(fetch、decode、execute、MEM、write back(结果到寄存器)。因此,当ID解码分支指令时,管道丢弃IF中当前获取的指令和ID中当前解码的指令(因为它是分支后的指令)

“危险”是指阻止稳定的指令流以每时钟一条的速度通过管道的情况。分支是一种控制(与流控制一样,与数据相反)

如果分支目标不在一级I-cache中,则管道必须等待指令从内存流入,然后If管道阶段才能生成回迁指令。始终创建管道气泡。对于非分支代码,预回迁通常可以避免这种情况


更复杂的CPU会提前解码,以检测分支,并很快重新引导回迁以隐藏此气泡。这可能涉及一个解码指令队列以隐藏回迁气泡

此外,CPU可以根据缓存检查每个指令地址,而不是实际解码以检测分支指令。如果命中,即使尚未解码该指令,也会知道该指令是分支。BTB还保留目标地址,因此可以立即从那里开始提取(如果是无条件分支,或者您的CPU支持基于分支预测的推测性执行)


ret
实际上是更难的情况:返回地址在寄存器或堆栈上,而不是直接编码到指令中。它是一个无条件的间接分支。现代x86 CPU维护一个内部返回地址预测器堆栈,当调用/ret指令不匹配时,执行得非常糟糕。例如
呼叫标签
/
标签:pop ebx
对于位置无关的32位代码将EIP引入ebx来说非常糟糕。这将导致错误预测呼叫树上大约15个
ret
s

我想我已经读过一些其他非x86微体系结构所使用的

请参阅以了解有关x86 CPU行为的更多信息(另请参阅tag wiki),或阅读计算机体系结构教科书以了解

有关缓存和内存(主要关注数据缓存/预取)的更多信息,请参阅


无条件分支非常便宜,最坏情况下通常是几个周期(不包括I-cache未命中)

函数调用的最大代价是编译器看不到目标函数的定义,并且必须假设它会在调用约定中删除所有调用删除的寄存器(在x86-64 SystemV中,所有浮点/向量寄存器以及大约8个整数寄存器)这需要溢出到内存或将实时数据保存在保留调用的寄存器中。但这意味着函数必须保存/恢复这些寄存器,以避免中断调用方

在同一个编译单元内,编译器可以进行过程间优化,让函数利用知道哪些寄存器实际上会阻塞其他函数,哪些不会阻塞其他函数的优势。甚至可以通过链接时间整个程序优化跨编译单元。但它不能跨动态链接边界扩展,因为不允许使用编译器生成会与同一共享库的不同编译版本冲突的代码


编译器有没有办法处理这个问题来减少它

它们内嵌小函数,甚至只调用一次的大型
静态
函数

正如@harold所指出的,过度使用内联也会导致缓存未命中,因为它会使代码的大小膨胀得太大,以至于不是所有的热代码都适合缓存


英特尔SnB系列设计有一个小型但非常快速的uop缓存,用于缓存解码指令。它最多只能保存1536个uop IIRC,每行6个uop。从uop缓存而不是从解码器执行可将分支预测失误惩罚从19个周期缩短到15个周期,IIRC(类似于这样,但这些数字对于任何特定的uarch可能实际上都不正确)。与解码器相比,前端吞吐量也有显著提升,尤其是对于向量码中常见的长指令。

通常跳转到函数并返回不是问题,而在条件下跳转则是(“分支”)…就像每次使用if/while/for/etc一样…这里有一篇关于这一点的很好的帖子:在现代处理器中,分支预测失误通常比缓存未命中要便宜。而编译器可以防止分支(从而防止预测失误)通过内联函数和展开循环,这大大增加了代码大小。虽然过去有一些方法可以帮助分支预测器,但前端应该重定向
int foo(void) { return 1; }
    mov     eax, 1    #,
    ret

int bar(int x) { return foo() + x;} 
    lea     eax, [rdi+1]      # D.2839,
    ret