0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

初探Golang内联

Linux爱好者 来源:稀土掘金技术社区 作者:ag9920 2022-12-13 09:51 次阅读

开篇

今天我们来聊聊 Golang 中的内联。

我们知道,函数调用本身是存在成本的。如果把一个实际调用的函数产生的指令,直接插入到的位置,来替换对应的函数调用指令。就可以消除掉这部分性能损耗。但同时也要注意,我们需要维护各个模块的可读性,需要保证高内聚,低耦合,不可能把所有逻辑合到一个函数,这样可读性大大降低。

那么,既然在代码层面做不太好,还有没有别的招呢?

内联就是来做这件事的。下面我们一起来看一下。

内联

所谓内联,指的是编译期间,直接将调用函数的地方替换为函数的实现,它可以减少函数调用的开销以提高程序的性能。内联函数是直接复制“镶嵌”到主函数中去的,就是将内联函数的代码直接放在内联函数的位置上,

这与一般函数不同,主函数在调用一般函数的时候,是指令跳转到被调用函数的入口地址,执行完被调用函数后,指令再跳转回主函数上继续执行后面的代码;而由于内联函数是将函数的代码直接放在了函数的位置上,所以没有指令跳转,指令按顺序执行。Go程序编译时,默认将进行内联优化。

当然,内联也并不是没有代价,这本质是一种以空间换时间的优化方法,其带来的优点是使CPU需要执行的指令数变少了,不需要根据地址跳转的过程了,不用压栈和出栈的过程了,我们把可以复用的程序指令在调用它的地方完全展开了。如果一个函数在很多地方都被调用了,那么就会展开很多次,整个程序占用的空间就会变大了。

需要注意,内联也是有门槛的,并不是随便一个函数调用都可以原地替换。Golang 编译器内部会有一套自己的判断规则,判断一次函数调用能否被内联,后面的章节我们会提到。这也是为什么我们会说:

Inlining is the act of combining smaller functions into their respective callers.

这个 small 的程度很关键。

简单小结一下,内联带来的好处有两个:

解除函数调用的开销,以空间换时间;

支持编译器更有效地应用其他优化策略。

函数调用开销

一个goroutine会有一个单独的栈,栈又会包含多个栈帧,栈帧是函数调用时在栈上为函数所分配的区域。函数调用存在一些固定开销:

创建栈帧;

读写寄存器

栈溢出检测

内联什么时候最有效

函数执行的开销 vs 函数调用的开销。这两个开销的比值会很大程度上决定【内联】的效果。

内联其实就是把函数调用这份固定开销给消除了,所以尤其对于函数体极其简单的函数有效果。如果你的函数执行了一系列复杂逻辑,开销远超【函数调用】本身,这里的优化就微不足道了。

内联虽然可以减少函数调用的开销,但是也可能因为存在重复代码,从而导致 CPU 缓存命中率降低,所以并不能盲目追求过度的内联,需要结合 profile 结果来具体分析。

Golang 编译器对内联的要求

参考官方 wiki:github.com/golang/go/w…[1]

a2e89d80-77b3-11ed-8abf-dac502259ad0.jpg

想要内联,方法本身必须满足以下条件:

函数足够简单,当解析AST时,Go申请了80个节点作为内联的预算。每个节点都会消耗一个预算。函数的开销不能超过这个预算;

不能包含闭包,defer,recover,select;

不能以 go:noinline 或 go:unitptrescapes 开头;

必须有函数体;

其他等复杂要求,详细可见src/cmd/compile/internal/gc/inl.go相关内容。我们可以使用 gcflags 参数来判断能不能内联。

内联的实现原理建议大家看看这篇文章:gocompiler.shizhz.me/8.-golang-b…[2]

如何禁止内联

单个函数级别:在函数定义前一行添加//go:noinline;

全局编译级别:可通过-gcflags="-l"选项全局禁用内联,与一个-l禁用内联相反,如果传递两个或两个以上的-l则会打开内联,并启用更激进的内联策略。

gcflags

go build 时可以使用 -gcflags 指定编译选项,-gcflags 参数的格式是:

-gcflags="pattern=arg list"

pattern 是选择包的模式,arg list 是空格分割的编译选项,如果编译选项中含有空格,可以使用引号包起来。

如:-gcflags="all=-N -l" 代表的是表示主模块和它所有的依赖都禁用【编译器优化】和【内联】。更多编译选项参照 go tool compile --help

Use -gcflags -m to observe the result of escape analysis and inlining decisions for the gc toolchain.

使用 go build 编译时,我们可以使用参数-gflags="-m"运行,可显示被内联的函数,使用运行参数-gflags="-m -m"可以看到原因。类似:

./main.go:14:6:cannotinlinexxx:unhandledopXXX

/ins.go:9:6:cannotinlinexxx:functiontoocomplex:cost104exceedsbudget80

我们可以用下面的命令分析变量是否逃逸:

gorun-gcflags'-m-l'main.go

-m 其实是打印优化策略的语义,实际上最多总共可以用 4 个 -m,但是信息量较大,一般用 1 个就可以了;

-l 会禁用函数内联,在这里禁用掉内联能更好的观察逃逸情况,减少干扰

内联后堆栈信息还对不对

内联会将函数调用的过程抹掉,这会引入一个新的问题:代码的堆栈信息还能否保证。其实这一点不用担心,Golang 内部会为每个存在内联优化的 goroutine 维持一个内联树(inlining tree),该树可通过 -gcflags="-d pctab=pctoinline" 命令查看,Go在生成的代码中映射了内联函数。并且,也映射了行号。这张表被嵌入到了二进制文件中,所以在运行时可以得到准确的堆栈信息。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 寄存器
    +关注

    关注

    31

    文章

    5343

    浏览量

    120328
  • 函数
    +关注

    关注

    3

    文章

    4331

    浏览量

    62595
  • 编译器
    +关注

    关注

    1

    文章

    1634

    浏览量

    49128

原文标题:初探 Golang 内联

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Golang接口的作用和应用场景

    Golang(Go)作为一门现代的静态类型编程语言,提供了许多强大的特性,其中之一便是接口(interface)。接口是Golang中的一个核心概念,它具有广泛的应用场景,可以帮助开发者实现
    的头像 发表于 12-05 10:44 1131次阅读

    如何使用Golang连接MySQL

    首先我们来看如何使用Golang连接MySQL。
    的头像 发表于 01-08 09:42 3376次阅读
    如何使用<b class='flag-5'>Golang</b>连接MySQL

    Golang怎么实现UTS隔离

    Golang实现UTS隔离
    发表于 08-23 14:44

    基于SUIF的函数内联技术

    从基于调用图的函数内联技术、函数参数的映射技术和内联使用的不同策略3 个方面讨论基于SUIF 系统的内联技术的实现。根据KAP 系统需求,提出叶节点的内联算法,以满足并行性分
    发表于 03-28 09:50 6次下载

    C++如何处理内联虚函数

    当一个函数是内联和虚函数时,会发生代码替换或使用虚表调用吗? 为了弄 清楚内联和虚函数,让我们将它们分开来考虑。通常,一个内联函数是被展开的 。 class CFoo {
    发表于 11-29 11:59 28次下载

    内联函数详解

    什么是内联性和外联函数 类的成员函数可以分为内联函数和外联函数。内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。而说明在类体内,定义在类体外的成员函数叫外联函数。外联函数的函数体
    发表于 11-02 14:05 0次下载

    内联函数和外联函数有什么区别

    内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是
    发表于 12-15 11:52 5869次阅读
    <b class='flag-5'>内联</b>函数和外联函数有什么区别

    使用golang channel的诸多特性和技巧

      本文介绍了使用 golang channel 的诸多特性和技巧,已经熟悉了 go 语言特性的小伙伴也可以看看,很有启发。
    的头像 发表于 09-06 15:14 1825次阅读
    使用<b class='flag-5'>golang</b> channel的诸多特性和技巧

    C++基础语法之inline 内联函数

    上节我们分析了C++基础语法的const,static以及 this 指针,那么这节内容我们来看一下 inline 内联函数吧! inline 内联函数 特征 相当于把内联函数里面的内容写在调用
    的头像 发表于 09-09 09:38 2149次阅读

    GoLang的安装和使用

    GoLang的安装和使用
    的头像 发表于 01-13 14:06 1275次阅读
    <b class='flag-5'>GoLang</b>的安装和使用

    讲解下C语言的内联函数

    内联函数是C语言从C++中借鉴过来的,适当的使用内联函数可以提高程序的执行效率。
    的头像 发表于 02-16 09:15 1500次阅读

    C语言内联函数,提升C技巧必备

    内联函数是C语言从C++中借鉴过来的,适当的使用内联函数可以提高程序的执行效率。本篇文章就来讲解下内联函数,赶紧来看下吧!
    的头像 发表于 02-16 09:16 792次阅读

    一个快速应用程序开发(RAD)工具(Golang版)

    SNMPAgent Builder(Golang版)是一个快速应用程序开发(RAD)工具,用于基于Golang 的 SNMP代理开发。提供了一个直观的图形用户界面,用于自动执行各种SNMP 代理开发任务
    的头像 发表于 04-13 09:30 1542次阅读

    【芒果派MangoPi MQ Quad】使用Golang点灯

    使用Golang在芒果派上点灯
    的头像 发表于 07-21 14:44 697次阅读
    【芒果派MangoPi MQ Quad】使用<b class='flag-5'>Golang</b>点灯

    宏的缺陷与内联函数的引入

    。 所以为了解决这种不利于调试的问题,就有了内联函数。 那么什么是内联函数呢? 我们以inline修饰的函数叫做内联函数,编译阶段,C编译器会在调用函数的地方直接把函数展开,没有压栈开销,内联
    的头像 发表于 11-01 17:57 445次阅读