大家好,我是炸鱼。
这是一篇文章,介绍了GO编译器如何意识到内部联盟以及该优化将如何影响您的GO代码。
接下来,我开始向炸鱼学习。
内部联合是各自呼叫者中较小功能的行为。它在不同计算的历史时期的实践是不同的,如下:
内部联盟非常重要,每种语言都将不可避免地可用。具体原因如下:
核心是性能更好。
以任何语言调用函数都是昂贵的。参数包含在寄存器或堆栈中(取决于ABI),并在返回时反转了此过程。这些是费用。
调用功能需要将程序计数器从指令流中的一个点跳到另一个点,这可能会导致汇编线停滞。一旦您输入该功能,通常需要准备一个新的堆栈框架来执行该功能。在返回呼叫者之前,需要类似的尾声来撤退此框架。
在GO中,函数的调用需要额外的费用来支持动态堆栈的增长。当输入时,可用于Goroutine的堆栈空间数与功能所需的数字进行比较。
如果可用的堆栈空间不足,则在运行时,序言将跳到逻辑,并通过将堆栈复制到新的更大的位置来增加堆栈。
一旦执行此操作,您将在运行时跳回原始功能的起点,再次执行堆栈检查,现在通过它,然后继续致电。这样,Goroutines可以从一个小的堆栈分配开始,然后它只有在需要时会增加。
这种检查非常便宜,只需要一些说明,并且由于Goroutine的堆栈几何增加,检查很少失败。因此,现代处理器中的分支预测单元可以通过假设堆栈检查是堆栈检查的成本来掩盖堆栈检查的成本始终成功地拥有运行时Goroutine堆栈的工作成本,与Goroutine堆栈在工作执行执行过程中所需的工作成本相比,管道停滞成本相对较小。
GO中的优化,尽管使用投机执行技术通过现代处理器很好地优化了每个功能调用的通用组件和GO特定组件的成本,但是这些开销无法完全消除。因此,每个功能调用都有性能成本,绩效成本超过执行有用工作的时间。由于函数调用的开销是固定的,因此较小的功能要比较大的功能支付更高的价格,因为它们通常每次都拨打较少的有用作业。
因此,消除这些费用的解决方案必须通过函数调用本身消除。通过将函数的函数替换为函数的调用,在某些条件下使用了GO编译器。该函数称为Inner United,因为它使函数保持函数与呼叫者一致。
Cliff Click博士描述了内部关节作为现代编译器的优化,因为它是优化的基础,例如持续的通信和消除代码消除。
实际上,内部联合允许编译器进一步查看,从而可以观察到可以进一步简化或完全消除的逻辑。
由于可以重新发布内部联盟,因此优化决策不仅可以在每个单独的功能的上下文中进行,而且可以用于呼叫路径中的功能链。
Neilian的效果可以通过这个小例子证明:
运行此基准测试以获取以下结果:
从执行结果的角度来看,成本约为2.24N,性能很好。
现在,让我们删除句子,然后查看不允许Neilian时是否会更改性能。
以下结果:
两个结果比较,2.24N和0.51N。差距至少翻了一番。根据Benchstat的建议,在内部关节的情况下,性能增长了78%。
以下结果:
首先,取消函数调用和相关的前指导操作是主要的改进贡献者。将最大功能的内容拉入了呼叫者中,减少了处理器执行的指令数量并消除了多个分支。
现在,编译器可见最大函数的内容。当它优化基准max时,它可以做出一些其他改进。
考虑到一旦最大值在内联合中,基准max的主体将更改为编译器,该编译器与用户所看到的不同。
以下代码:
再次运行基准测试,我们看到手动内部对联的版本与编译器内部部分的版本一样好。
以下结果:
现在,编译器可以获取最大连接到基准max的结果,该结果可以应用于以前不太可能的优化方法。
例如:编译器注意到我被初始化为0,并且只增加了,因此与我可以假设我永远不会负面的任何比较。因此,条件永远不会是真实的。
在证明它永远不会正确之后,编译器可以将代码简化为:
而且由于分支现在是一个常数,因此编译器可以消除无法到达路径,仅留下以下代码:
通过优化内部联合及其释放,编译器将表达式简化为。
这个示例非常好,反映出内部结合的优化过程和性能改善的原因非常好。
在本文中,讨论了SO所谓的叶子内对联:呼叫对堆栈底部的直接呼叫的行为。
内部结合是一个递归过程。一旦将函数连接到呼叫者,编译器就可以将生成的代码连接到其呼叫者并根据此将其推开。
例如,以下代码:
操作速度将与上一个示例一样快,因为编译器可以反复应用上述优化以将代码降低到同一表达式。
本文对内海内部进行了基本概念介绍和分析,并逐步分析了GO的示例,以便每个人都对真实情况有更适当的理解。
GO编译器的优化始终无处不在。
文章不断更新,您可以搜索[炸鱼的大脑]要阅读,本文githubgithub.com/eddycjy/blog已包括在内。您可以阅读GO语言以查看GO学习图和路线。
原始:https://juejin.cn/post/7111176199174357023