iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Go语言中怎么对栈中函数进行内联
  • 275
分享到

Go语言中怎么对栈中函数进行内联

2023-06-16 13:06:20 275人浏览 安东尼
摘要

这篇文章主要介绍“Go语言中怎么对栈中函数进行内联”,在日常操作中,相信很多人在Go语言中怎么对栈中函数进行内联问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中怎么对栈中函数进行内联”的疑惑有所帮助!

这篇文章主要介绍“Go语言中怎么对栈中函数进行内联”,在日常操作中,相信很多人在Go语言中怎么对栈中函数进行内联问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中怎么对栈中函数进行内联”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

内联的限制

把函数内联到它的调用处消除了调用的开销,为编译器进行其他的优化提供了更好的机会,那么问题来了,既然内联这么好,内联得越多开销就越少,为什么不尽可能多地内联呢?

内联可能会以增加程序大小换来更快的执行时间。限制内联的最主要原因是,创建许多函数的内联副本会增加编译时间,并导致生成更大的二进制文件的边际效应。即使把内联带来的进一步的优化机会考虑在内,太激进的内联也可能会增加生成的二进制文件的大小和编译时间。

内联收益最大的是小函数,相对于调用它们的开销来说,这些函数做很少的工作。随着函数大小的增长,函数内部做的工作与函数调用的开销相比省下的时间越来越少。函数越大通常越复杂,因此优化其内联形式相对于原地优化的好处会减少。

内联预算

在编译过程中,每个函数的内联能力是用内联预算计算的 1。开销的计算过程可以巧妙地内化,像一元和二元等简单操作,在抽象语法数Abstract Syntax Tree(AST)中通常是每个节点一个单位,更复杂的操作如 make 可能单位更多。考虑下面的例子:

package main func small() string {    s := "hello, " + "world!"    return s} func large() string {    s := "a"    s += "b"    s += "c"    s += "d"    s += "e"    s += "f"    s += "g"    s += "h"    s += "i"    s += "j"    s += "k"    s += "l"    s += "m"    s += "n"    s += "o"    s += "p"    s += "q"    s += "r"    s += "s"    s += "t"    s += "u"    s += "v"    s += "w"    s += "x"    s += "y"    s += "z"    return s} func main() {    small()    large()}

使用 -GCflags=-m=2 参数编译这个函数能让我们看到编译器分配给每个函数的开销:

% go build -gcflags=-m=2 inl.go# command-line-arguments./inl.go:3:6: can inline small with cost 7 as: func() string { s := "hello, world!"; return s }./inl.go:8:6: cannot inline large: function too complex: cost 82 exceeds budget 80./inl.go:38:6: can inline main with cost 68 as: func() { small(); large() }./inl.go:39:7: inlining call to small func() string { s := "hello, world!"; return s }

编译器根据函数 func small() 的开销(7)决定可以对它内联,而 func large() 的开销太大,编译器决定不进行内联。func main() 被标记为适合内联的,分配了 68 的开销;其中 small 占用 7,调用 small 函数占用 57,剩余的(4)是它自己的开销。

可以用 -gcflag=-l 参数控制内联预算的等级。下面是可使用的值:

  • -gcflags=-l=0 默认的内联等级。

  • -gcflags=-l(或 -gcflags=-l=1)取消内联。

  • -gcflags=-l=2-gcflags=-l=3 现在已经不使用了。和 -gcflags=-l=0 相比没有区别。

  • -gcflags=-l=4 减少非叶子函数和通过接口调用的函数的开销。2

不确定语句的优化

一些函数虽然内联的开销很小,但由于太复杂它们仍不适合进行内联。这就是函数的不确定性,因为一些操作的语义在内联后很难去推导,如 recoverbreak。其他的操作,如 selectgo 涉及运行时的协调,因此内联后引入的额外的开销不能抵消内联带来的收益。

不确定的语句也包括 forrange,这些语句不一定开销很大,但目前为止还没有对它们进行优化。

栈中函数优化

在过去,Go 编译器只对叶子函数进行内联 —— 只有那些不调用其他函数的函数才有资格。在上一段不确定的语句的探讨内容中,一次函数调用就会让这个函数失去内联的资格。

进入栈中进行内联,就像它的名字一样,能内联在函数调用栈中间的函数,不需要先让它下面的所有的函数都被标记为有资格内联的。栈中内联是 David Lazar 在 Go 1.9 中引入的,并在随后的版本中做了改进。这篇文稿深入探究了保留栈追踪行为和被深度内联后的代码路径里的 runtime.Callers 的难点。

在前面的例子中我们看到了栈中函数内联。内联后,func main() 包含了 func small() 的函数体和对 func large() 的一次调用,因此它被判定为非叶子函数。在过去,这会阻止它被继续内联,虽然它的联合开销小于内联预算。

栈中内联的最主要的应用案例就是减少贯穿函数调用栈的开销。考虑下面的例子:

package main import (    "fmt"    "strconv") type Rectangle struct {} //go:noinlinefunc (r *Rectangle) Height() int {    h, _ := strconv.ParseInt("7", 10, 0)    return int(h)} func (r *Rectangle) Width() int {    return 6} func (r *Rectangle) Area() int { return r.Height() * r.Width() } func main() {    var r Rectangle    fmt.Println(r.Area())}

在这个例子中, r.Area() 是个简单的函数,调用了两个函数。r.Width() 可以被内联,r.Height() 这里用 //go:noinline 指令标注了,不能被内联。3

% go build -gcflags='-m=2' square.go                                                                                                          # command-line-arguments./square.go:12:6: cannot inline (*Rectangle).Height: marked go:noinline                                                                               ./square.go:17:6: can inline (*Rectangle).Width with cost 2 as: method(*Rectangle) func() int { return 6 }./square.go:21:6: can inline (*Rectangle).Area with cost 67 as: method(*Rectangle) func() int { return r.Height() * r.Width() }                       ./square.go:21:61: inlining call to (*Rectangle).Width method(*Rectangle) func() int { return 6 }                                                     ./square.go:23:6: cannot inline main: function too complex: cost 150 exceeds budget 80                        ./square.go:25:20: inlining call to (*Rectangle).Area method(*Rectangle) func() int { return r.Height() * r.Width() }./square.go:25:20: inlining call to (*Rectangle).Width method(*Rectangle) func() int { return 6 }

由于 r.Area() 中的乘法与调用它的开销相比并不大,因此内联它的表达式是纯收益,即使它的调用的下游 r.Height() 仍是没有内联资格的。

快速路径内联

关于栈中内联的效果最令人吃惊的例子是 2019 年 Carlo Alberto Ferraris 通过允许把 sync.Mutex.Lock() 的快速路径(非竞争的情况)内联到它的调用方来提升它的性能。在这个修改之前,sync.Mutex.Lock() 是个很大的函数,包含很多难以理解的条件,使得它没有资格被内联。即使可用时,调用者也要付出调用 sync.Mutex.Lock() 的代价。

Carlo 把 sync.Mutex.Lock() 分成了两个函数(他自己称为外联outlining)。外部的 sync.Mutex.Lock() 方法现在调用 sync/atomic.CompareAndSwapint32() 且如果 CAS(比较并交换Compare and Swap)成功了之后立即返回给调用者。如果 CAS 失败,函数会走到 sync.Mutex.lockSlow() 慢速路径,需要对锁进行注册,暂停 goroutine。4

% go build -gcflags='-m=2 -l=0' sync 2>&1 | grep '(*Mutex).Lock'../go/src/sync/mutex.go:72:6: can inline (*Mutex).Lock with cost 69 as: method(*Mutex) func() { if "sync/atomic".CompareAndSwapInt32(&m.state, 0, mutexLocked) { if race.Enabled {  }; return  }; m.lockSlow() }

通过把函数分割成一个简单的不能再被分割的外部函数,和(如果没走到外部函数就走到的)一个处理慢速路径的复杂的内部函数,Carlo 组合了栈中函数内联和编译器对基础操作的支持,减少了非竞争锁 14% 的开销。之后他在 sync.RWMutex.Unlock() 重复这个技巧,节省了另外 9% 的开销。

到此,关于“Go语言中怎么对栈中函数进行内联”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

--结束END--

本文标题: Go语言中怎么对栈中函数进行内联

本文链接: https://www.lsjlt.com/news/283641.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Go语言中怎么对栈中函数进行内联
    这篇文章主要介绍“Go语言中怎么对栈中函数进行内联”,在日常操作中,相信很多人在Go语言中怎么对栈中函数进行内联问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中怎么对栈中函数进行内联”的疑惑有所帮助!...
    99+
    2023-06-16
  • Go语言中怎么对栈进行处理
    本篇文章为大家展示了Go语言中怎么对栈进行处理,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。一、线程栈(thread stacks)介绍在我们研究Go的栈处理方式之...
    99+
    2024-04-02
  • Go语言中怎么对Concurrency进行管理
    这篇文章给大家介绍 Go语言中怎么对Concurrency进行管理 ,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。WaitGroup先来了解有什么情境需要使用到  WaitG...
    99+
    2024-04-02
  • Go语言中的函数怎么调用
    本篇内容介绍了“Go语言中的函数怎么调用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1.函数的声明定义//func关键字//getStud...
    99+
    2023-07-02
  • 对Go语言中的数组数据结构进行深入分析
    数组数据结构: 数组是一种基本的数据结构,它包含一系列元素,每个元素都有一个索引。数组中的元素可以是任何类型,包括其他数组。数组的大小在创建时确定,并且在以后不能改变。 代码示例:// 创建一个包含 5 个整...
    99+
    2024-02-01
    数据结构 解读 go数组 go语言
  • C#中内联函数怎么用
    这篇文章将为大家详细讲解有关C#中内联函数怎么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。C++ 中有个内联函数,使用 inline 来修饰函数,编译器就会对其进行优化,将此函数作为代码判断插入到调用...
    99+
    2023-06-29
  • 怎么在R语言中对数据进行重新编码
    怎么在R语言中对数据进行重新编码?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。(一)使用逻辑判断式(1)现假设我们需要将下面的连续型变量x按照10与20分成三个...
    99+
    2023-06-14
  • 怎么在java8中对函数进行引用
    怎么在java8中对函数进行引用?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。函数引用的类型函数引用分为以下四种:静态函数,比如 Integer 类的 pars...
    99+
    2023-05-31
    java8 中对 ava
  • 怎么在Go语言中使用JSON进行请求
    这篇文章主要介绍“怎么在Go语言中使用JSON进行请求”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么在Go语言中使用JSON进行请求”文章能帮助大家解决问题。Go语言提供了许多方式发送HTTP请...
    99+
    2023-07-06
  • C语言中函数参数的入栈顺序
    这篇文章主要介绍“C语言中函数参数的入栈顺序”,在日常操作中,相信很多人在C语言中函数参数的入栈顺序问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C语言中函数参数的入栈顺序”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-17
  • 详解Go语言中的内存对齐
    目录前言基础知识看个问题什么是内存对齐为什么需要内存对齐unsafe.AlignOf()内存对齐规则举个例子空结构体的对齐规则总结前言 前面有篇文章我们学习了 Go 语言空...
    99+
    2024-04-02
  • r语言中怎么进行数据清洗
    在R语言中进行数据清洗,可以按照以下步骤进行: 缺失值处理:使用函数is.na()判断缺失值,使用函数na.omit()删除包含...
    99+
    2024-03-06
    r语言
  • r语言中怎么进行数据处理
    在R语言中进行数据处理时,常见的操作包括数据清洗、数据转换、数据筛选、数据聚合、数据可视化等。以下是一些常用的数据处理操作: 数据...
    99+
    2024-03-02
    r语言
  • 对Go语言中数组的定义和使用进行深入剖析
    Go语言中数组的定义与用法探析 数组定义 Go语言中的数组是一种有序的固定长度的数据结构,可以存储相同类型的数据元素。数组的元素可以通过索引来访问,索引从0开始。 数组的定义语法如下:var arrayNam...
    99+
    2024-02-01
    go语言 数组用法 数组定义
  • R语言中怎么进行数据筛选
    在R语言中,可以使用subset()函数来进行数据筛选。subset()函数的参数包括数据框(data frame)对象和逻辑条件,...
    99+
    2024-03-02
    R语言
  • 如何使用 Go 语言对数组进行排序?
    Go 语言是一门现代化的编程语言,它拥有良好的并发支持和高效的内存管理,被广泛应用于网络编程、分布式系统、云计算等领域。在 Go 语言中,数组是一种常见的数据结构,它可以用来存储一组具有相同类型的元素。本文将介绍如何使用 Go 语言对数组进...
    99+
    2023-10-07
    数组 git 日志
  • Go语言中怎么使用HTTPS协议进行请求
    这篇文章主要介绍“Go语言中怎么使用HTTPS协议进行请求”,在日常操作中,相信很多人在Go语言中怎么使用HTTPS协议进行请求问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Go语言中怎么使用HTTPS协议进...
    99+
    2023-07-06
  • Go语言中make和new函数怎么使用
    这篇文章主要讲解了“Go语言中make和new函数怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go语言中make和new函数怎么使用”吧!相同点:make和new都是用来创建分配类...
    99+
    2023-07-02
  • go语言中的输入函数怎么使用
    这篇“go语言中的输入函数怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“go语言中的输入函数怎么使用”文章吧。go语...
    99+
    2023-07-04
  • go语言怎么进行web开发
    Go语言的Web开发流程为:1、选择Web框架;2、设计路由,指定URL如何映射到处理程序;3、处理请求和响应,包括解析请求参数、处理表单数据、设置Cookie等;4、数据库操作,将数据存储在数据库中;5、模板渲染,将数据呈现给用户使用模板...
    99+
    2023-12-13
    go语言web开发 go语言 Golang
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作