Go 内联优化让程序员爱不释手
IT行业相对于一般传统行业,发展更新速度更快,一旦停止了学习,很快就会被行业所淘汰。所以我们需要踏踏实实的不断学习,精进自己的技术,尤其是初学者。今天golang学习网给大家整理了《Go 内联优化让程序员爱不释手》,聊聊优化、内联,我们一起来看看吧!
前言:
这是一篇介绍 Go 编译器如何实现内联的文章,以及这种优化将如何影响你的 Go 代码。
什么是内联?
内联是将较小的函数合并到它们各自的调用者中的行为。其在不同的计算历史时期的做法不一样,如下:
- 早期:这种优化通常是由手工完成的。
- 现在:内联是在编译过程中自动进行的一类基本优化之一。
为什么内联很重要?
内联是很重要的,每一门语言都必然会有。
具体的原因如下:
- 它消除了函数调用本身的开销。
- 它允许编译器更有效地应用其他优化策略。
核心来讲,就是性能更好了。
函数调用的开销
基本知识
在任何语言中调用一个函数都是有代价的。将参数编入寄存器或堆栈(取决于ABI),并在返回时反转这一过程,这些都是开销。
调用一个函数需要将程序计数器从指令流中的一个点跳到另一个点,这可能会导致流水线停滞。一旦进入函数,通常需要一些前言来为函数的执行准备一个新的堆栈框架,在返回调用者之前,还需要一个类似的尾声来退掉这个框架。
Go 中的开销
在 Go 中,一个函数的调用需要额外的成本来支持动态堆栈的增长。在进入时,goroutine 可用的堆栈空间的数量与函数所需的数量进行比较。
如果可用的堆栈空间不足,序言就会跳转到运行时逻辑,通过将堆栈复制到一个新的、更大的位置来增加堆栈。
一旦这样做了,运行时就会跳回到原始函数的起点,再次进行堆栈检查,现在通过了,然后继续调用。通过这种方式,goroutines可以从一个小的堆栈分配开始,只有在需要时才会增加。
这种检查很便宜,只需要几条指令,而且由于goroutine的堆栈以几何级数增长,检查很少失败。因此,现代处理器中的分支预测单元可以通过假设堆栈检查总是成功来隐藏堆栈检查的成本。在处理器错误预测堆栈检查并不得不丢弃它在投机执行时所做的工作的情况下,与运行时增长goroutine堆栈所需的工作成本相比,管道停滞的成本相对较小。
Go 里的优化
虽然每个函数调用的通用组件和 Go 特定组件的开销被使用投机执行技术的现代处理器很好地优化了,但这些开销不能完全消除,因此每个函数调用都带有性能成本,超过了执行有用工作的时间。由于函数调用的开销是固定的,较小的函数相对于较大的函数要付出更大的代价,因为它们每次调用的有用工作往往较少。
因此,消除这些开销的解决方案必须是消除函数调用本身,Go 编译器在某些条件下通过用函数的内容替换对函数的调用来做到这一点。这被称为内联,因为它使函数的主体与它的调用者保持一致。
改善优化的机会
Cliff Click 博士将内联描述为现代编译器进行的优化,因为它是常量传播和死代码消除等优化的基础。
实际上,内联允许编译器看得更远,允许它在特定函数被调用的情况下,观察到可以进一步简化或完全消除的逻辑。
由于内联可以递归应用,优化决策不仅可以在每个单独的函数的上下文中做出,还可以应用于调用路径中的函数链。
进行内联优化
不允许内联
内联的效果可以通过这个小例子来证明:
package main
import "testing"
//go:noinline
func max(a, b int) int {
if a > b {
return a
}
return b
}
var Result int
func BenchmarkMax(b *testing.B) {
var r int
for i := 0; i
<p><strong>运行这个基准可以得到以下结果:</strong></p>
<blockquote><p>% go test -bench=. <br>BenchmarkMax-4 530687617 2.24 ns/op</p></blockquote>
<p>从执行结果来看,<code>max(-1, i)</code>的成本大约是 2.24ns,感觉性能不错。</p>
<h3>允许内联</h3>
<p>现在让我们去掉 <code>//go:noinline pragma</code> 的语句,再看看不允许内联的情况下,性能是否会改变。</p>
<p><strong>如下结果:</strong></p>
<blockquote><p>% go test -bench=. <br>BenchmarkMax-4 1000000000 0.514 ns/op</p></blockquote>
<p>两个结果对比一看,2.24ns 和 0.51ns。差距至少一倍以上,根据 benchstat 的建议,内联情况下,性能提高了 78%。</p>
<p><strong>如下结果:</strong></p>
<blockquote><p>% benchstat {old,new}.txt<br>name old time/op new time/op delta<br>Max-4 2.21ns ± 1% 0.49ns ± 6% -77.96% (p=0.000 n=18+19)</p></blockquote>
<h2>这些改进从何而来?</h2>
<p>首先,取消函数调用和相关的前导动作是主要的改进贡献者。其将 max 函数的内容拉到它的调用者中,减少了处理器执行的指令数量,并消除了几个分支。</p>
<p>现在 max 函数的内容对编译器来说是可见的,当它优化 BenchmarkMax 时,它可以做一些额外的改进。</p>
<p>考虑到一旦 max 被内联,BenchmarkMax 的主体对编译器而言就会有所改变,与用户端看到的并不一样。</p>
<p><strong>如下代码:</strong></p>
<pre class="brush:go;">func BenchmarkMax(b *testing.B) {
var r int
for i := 0; i i {
r = -1
} else {
r = i
}
}
Result = r
}
再次运行基准测试,我们看到我们手动内联的版本与编译器内联的版本表现一样好。
如下结果:
% benchstat {old,new}.txt
name old time/op new time/op delta
Max-4 2.21ns ± 1% 0.48ns ± 3% -78.14% (p=0.000 n=18+18)
现在,编译器可以获得 max 内联到 BenchmarkMax 的结果,它可以应用以前不可能的优化方法。
例如:编译器注意到 i 被初始化为 0,并且只被递增,所以任何与 i 的比较都可以假定 i 永远不会是负数。因此,条件 -1 > i 将永远不会为真。
在证明了 -1 > i 永远不会为真之后,编译器可以将代码简化为:
func BenchmarkMax(b *testing.B) {
var r int
for i := 0; i
<p>并且由于该分支现在是一个常数,编译器可以消除无法到达的路径,<strong>只留下如下代码:</strong></p>
<pre class="brush:go;">func BenchmarkMax(b *testing.B) {
var r int
for i := 0; i
<p>通过内联和它所释放的优化,编译器已经将表达式 <code>r = max(-1, i)</code> 简化为 <code>r = i</code>。</p>
<p>这个例子非常不错,很好的体现了内联的优化过程和性能提升的缘由。</p>
<h2>内联的限制</h2>
<p>在这篇文章中,讨论了所谓的叶子内联:将调用栈底部的一个函数内联到其直接调用者中的行为。</p>
<p>内联是一个递归的过程,一旦一个函数被内联到它的调用者中,编译器就可能将产生的代码内联到它的调用者中,依此类推。</p>
<p><strong>例如如下代码:</strong></p>
<pre class="brush:go;">func BenchmarkMaxMaxMax(b *testing.B) {
var r int
for i := 0; i
<p>该运行速度将会和前面的例子一样快,因为编译器能够反复应用上面的优化,将代码减少到相同的 <code>r = i</code> 表达式。</p>
<h2>总结</h2>
<p>这篇文章针对内联进行了基本的概念介绍和分析,并且通过 Go 的例子进行了一步步的剖析,让大家对真实案例有了一个更贴切的理解。</p>
<p>Go 编译器的优化总是无处不在的。</p>
<p>本篇关于《Go 内联优化让程序员爱不释手》的介绍就到此结束啦,但是学无止境,想要了解学习更多关于Golang的相关知识,请关注golang学习网公众号!</p>
Go本地测试小技巧解耦任务拆解
- 上一篇
- Go本地测试小技巧解耦任务拆解
- 下一篇
- GoFrame框架数据校验之校验结果Error接口对象
-
- Golang · Go教程 | 2分钟前 |
- Golang多协程通信技巧分享
- 255浏览 收藏
-
- Golang · Go教程 | 22分钟前 |
- Golang如何判断变量类型?
- 393浏览 收藏
-
- Golang · Go教程 | 30分钟前 |
- Golang云原生微服务实战教程
- 310浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang迭代器与懒加载结合应用
- 110浏览 收藏
-
- Golang · Go教程 | 1小时前 | 性能优化 并发安全 Golangslicemap 预设容量 指针拷贝
- Golangslicemap优化技巧分享
- 412浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang代理模式与访问控制实现解析
- 423浏览 收藏
-
- Golang · Go教程 | 1小时前 |
- Golang事件管理模块实现教程
- 274浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- Golang接口多态实现全解析
- 241浏览 收藏
-
- Golang · Go教程 | 2小时前 |
- GolangHTTP优化与中间件组合技巧
- 365浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3162次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3375次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3403次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4506次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3784次使用
-
- 分析Go错误处理优化go recover机制缺陷
- 2023-01-01 483浏览
-
- gozero微服务高在请求量下如何优化
- 2023-01-01 268浏览
-
- MySQL 8.0 对 limit 的优化技巧
- 2023-01-07 130浏览
-
- MySQL8.0 索引优化invisible index详情
- 2023-01-07 309浏览
-
- 分库分表实战:小试牛刀—千万级数据之SQL优化
- 2023-01-16 154浏览

