Go 内联优化让程序员爱不释手

目录
  • 前言:
  • 什么是内联?
  • 为什么内联很重要?
  • 函数调用的开销
    • 基本知识
    • Go 中的开销
    • 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 < b.N; i++ {
        r = max(-1, i)
    }
    Result = r
}

运行这个基准可以得到以下结果:

% go test -bench=. 
BenchmarkMax-4   530687617         2.24 ns/op

从执行结果来看,max(-1, i)的成本大约是 2.24ns,感觉性能不错。

允许内联

现在让我们去掉 //go:noinline pragma 的语句,再看看不允许内联的情况下,性能是否会改变。

如下结果:

% go test -bench=. 
BenchmarkMax-4   1000000000         0.514 ns/op

两个结果对比一看,2.24ns 和 0.51ns。差距至少一倍以上,根据 benchstat 的建议,内联情况下,性能提高了 78%。

如下结果:

% benchstat {old,new}.txt
name   old time/op  new time/op  delta
Max-4  2.21ns ± 1%  0.49ns ± 6%  -77.96%  (p=0.000 n=18+19)

这些改进从何而来?

首先,取消函数调用和相关的前导动作是主要的改进贡献者。其将 max 函数的内容拉到它的调用者中,减少了处理器执行的指令数量,并消除了几个分支。

现在 max 函数的内容对编译器来说是可见的,当它优化 BenchmarkMax 时,它可以做一些额外的改进。

考虑到一旦 max 被内联,BenchmarkMax 的主体对编译器而言就会有所改变,与用户端看到的并不一样。

如下代码:

func BenchmarkMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        if -1 > 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 < b.N; i++ {
        if false {  // 注意已为 false
            r = -1
        } else {
            r = i
        }
    }
    Result = r
}

并且由于该分支现在是一个常数,编译器可以消除无法到达的路径,只留下如下代码:

func BenchmarkMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        r = i
    }
    Result = r
}

通过内联和它所释放的优化,编译器已经将表达式 r = max(-1, i) 简化为 r = i

这个例子非常不错,很好的体现了内联的优化过程和性能提升的缘由。

内联的限制

在这篇文章中,讨论了所谓的叶子内联:将调用栈底部的一个函数内联到其直接调用者中的行为。

内联是一个递归的过程,一旦一个函数被内联到它的调用者中,编译器就可能将产生的代码内联到它的调用者中,依此类推。

例如如下代码:

func BenchmarkMaxMaxMax(b *testing.B) {
    var r int
    for i := 0; i < b.N; i++ {
        r = max(max(-1, i), max(0, i))
    }
    Result = r
}

该运行速度将会和前面的例子一样快,因为编译器能够反复应用上面的优化,将代码减少到相同的 r = i 表达式。

总结

这篇文章针对内联进行了基本的概念介绍和分析,并且通过 Go 的例子进行了一步步的剖析,让大家对真实案例有了一个更贴切的理解。

Go 编译器的优化总是无处不在的。

到此这篇关于Go 内联优化让程序员爱不释手的文章就介绍到这了,更多相关Go 内联优化内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-06-20

Django serializer优化类视图的实现示例

一. create优化 在serializer序列化中,我们通过创建序列化器对象的方式地简化了视图函数的代码,前端传入的数据通过反序列化操作进行了各种数据校验,代码如下: from django.http import JsonResponse from django.views import View import json from .models import Project from .serializers import ProjectsSerializer class Project

浅谈优化Django ORM中的性能问题

Django是个好工具,使用的很广泛. 在应用比较小的时候,会觉得它很快,但是随着应用复杂和壮大,就显得没那么高效了.当你了解所用的Web框架一些内部机制之后,才能写成比较高效的代码. 怎么查问题 Web系统是个挺复杂的玩意,有时候有点无从下手哈.可以采用 自底向上 的顺序,从数据存储一直到数据展现,按照这个顺序一点一点查找性能问题. 数据库 (缺少索引/数据模型) 数据存储接口 (ORM/低效的查询) 展现/数据使用 (Views/报表等) Web应用的大部分问题都会跟 数据库 扯上关系.除非

Django项目优化数据库操作总结

目录 合理的创建索引 设置数据库持久连接 减少SQL的执行次数 仅获取需要的字段数据 使用批量创建.更新和删除,不随意对结果排序 参考网址:Django官方数据库优化 使用 QuerySet.explain() 来了解你的数据库是如何执行特定的 QuerySet 的. 你可能还想使用一个外部项目,比如 django-debug-toolbar ,或者一个直接监控数据库的工具. 合理的创建索引 索引可能有助于加快查询速度,但是也要注意索引会占用磁盘空间,创建不必要的索引只会形成浪费.数据库表中的主

Django程序的优化技巧

友情提示: 过度性能优化是没有必要甚至有害的,因为花大力气带来的毫秒级的响应提升你的用户可能根本感知不到,毕竟开发人员的时间也很宝贵. 性能优化指标 在对一个Web项目进行性能优化时,我们通常需要评价多个指标: 响应时间 最大并发连接数 代码的行数 函数调用次数 内存占用情况 CPU占比 其中响应时间(服务器从接收用户请求,处理该请求并返回结果所需的总的时间)通常是最重要的指标,因为过长的响应时间会让用户厌倦等待,转投其它网站或APP.当你的用户数量变得非常庞大,如何提高最大并发连接数,减少内存

go select编译期的优化处理逻辑使用场景分析

前言 select作为Go chan通信的重要监听工具,有着很广泛的使用场景.select的使用主要是搭配通信case使用,表面上看,只是简单的select及case搭配,实际上根据case的数量及类型,在编译时select会进行优化处理,根据不同的情况调用不同的底层逻辑. select的编译处理 select编译时的核心处理逻辑如下: func walkselectcases(cases *Nodes) []*Node { ncas := cases.Len() sellineno := li

详解Django中views数据查询使用locals()函数进行优化

优化场景 利用视图函数(views)查询数据之后可以通过上下文context.字典.列表等方式将数据传递给HTML模板,由template引擎接收数据并完成解析.但是通过context传递数据可能就存在在不同的视图函数中使用重复的查询语句,所以可以通过将重复查询语句设置全局变量,配合locals()函数进行数据查询与传递. 优化前 def index(request): threatname = '威胁情报展示' url = 'www.testtip.com' allthreat = Threa

GoFrame代码优化gconv类型转换避免重复定义map

目录 前言 核心重点 优化前 优化后: 可以这么写的原因 进一步优化 批量写入 更优雅的写法如下 总结 前言 最近一直在研究 GoFrame 框架,经过一段时间的使用.总结.思考,发现确实不失为一款非常值得使用的企业级开发框架. 在我初识GoFrame教程后,曾整理过一篇文章: 非常适合PHP同学学习的GO框架:GoFrame,有兴趣的同学可以阅读一下. 今天重点讲一下我使用GoFrame的代码优化之旅. 核心重点 GoFrame几乎封装了所有能封装的东西,而我们需要做的就是在框架的基础上约定好

python3 googletrans超时报错问题及翻译工具优化方案 附源码

一. 问题: 在写调用谷歌翻译接口的脚本时,老是报错,我使用的的是googletrans这个模块中Translator的translate方法,程序运行以后会报访问超时错误: Traceback (most recent call last): File "E:/PycharmProjects/MyProject/Translate/translate_test.py", line 3, in <module> result=translator.translate('안녕

MongoDB数据库安装部署及警告优化

目录 1.软件下载 2.部署MongoDB 2.1.规划部署目录 2.2.下载软件包 2.3.安装MongoDB 2.4.MongoDB配置文件介绍 2.5.编写MongoDB配置文件 2.6.启动MongoDB 2.7.如何关闭MongoDB 2.8.登录MongoDB 3.优化MongoDB警告信息 3.1.优化启动用户警告 3.2.优化大内存页警告 3.2.1.永久关闭大内存页 3.2.2.临时关闭大内存页 3.3.优化limit警告 1.软件下载 3.6.13版本:https://fas

MongoDB数据库安装配置、基本操作实例详解

本文实例讲述了MongoDB数据库安装配置.基本操作.分享给大家供大家参考,具体如下: 1.简介 NO SQL:NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",是对不同于传统的关系数据库管理系统(RDBMS)的统称.NoSQL用于超大规模数据的存储,这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展. RDBMS NOSQL - 高度组织化结构化数据 - 结构化查询语言(SQL) - 数据和关系都存储在单独的表中. - 数据操纵语言,数据定

MongoDB入门教程之Windows下的MongoDB数据库安装图解

关于mongodb的好处,优点之类的这里就不说了,唯一要讲的一点就是mongodb中有三元素:数据库,集合,文档,其中"集合" 就是对应关系数据库中的"表","文档"对应"行".  一: 下载 上MongoDB官网 ,我们发现有32bit和64bit,这个就要看你系统了,不过这里有两点注意: ①:根据业界规则,偶数为"稳定版"(如:1.6.X,1.8.X),奇数为"开发版"(如:1.7.

window平台安装MongoDB数据库图文详解

本文介绍了window平台安装MongoDB数据库的详情,具体如下 官网:www.mongodb.org 安装-->设置环境变量-->启动 1.下载: 根据系统下载 32 位或 64 位的 .msi 文件,下载后双击该文件,按提示安装即可, 国外网站打开运行速度比较慢.现在给个国内下载地址.http://www.jb51.net/softs/41751.html 2.设置安装目录: 通过点击 "Custom(自定义)" 按钮来设置你的安装目录,我将其安装在D:\MongoD

Centos7安装和卸载Mongodb数据库的方法

MongoDB是一个基于分布式文件存储的数据库.由C++语言编写.旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的.他支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型.Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引. 本资料根据官方网站上总

windows与mac安装mongodb数据库的方法步骤与注意事项

nosql数据库 --mongodb数据库! Mongo DB 是目前在IT行业非常流行的一种非关系型数据库(NoSql),其灵活的数据存储方式备受当前IT从业人员的青睐.Mongo DB很好的实现了面向对象的思想(OO思想),在Mongo DB中每一条记录都是一个Document对象.Mongo DB最大的优势在于所有的数据持久操作都无需开发人员手动编写SQL语句,直接调用方法就可以轻松的实现CRUD操作. windows 安装 这里我说的是命令行安装方法 准备.到官网下载相应的版本 http

mongodb数据库入门学习笔记之下载、安装、启动、连接操作解析

本文实例讲述了mongodb数据库下载.安装.启动.连接操作.分享给大家供大家参考,具体如下: 简介: MongoDB 是一个基于分布式文件存储的数据库.由 C++ 语言编写.旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. 1.下载 从官网下载压缩包, 官网地址:https://www.mongodb.com/download-center/v2/community. 下载命

Win10 64位安装MongoDB数据库的详细教程

选择对应的系统并下载MongoDB https://www.mongodb.com/download-center/community 选择操作系统对应的版本 我是win10 64位,所以选择如下版本: 下载完的安装包: 双击安装时,选择自定义,可以修改安装目录: 我修改目录如下: D:\Program Files\MongoDB\Server\4.2\ 安装过程中的配置选择 服务配置说明 Run Service as Network Service user 以网络服务用户身份运行服务(默认)

python数据库开发之MongoDB安装及Python3操作MongoDB数据库详细方法与实例

MongoDB简介 MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoDB 旨在为WEB应用提供可扩展的高性能数据存储解决方案. MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成.MongoDB 文档类似于 JSON 对象.字段值可以包含其他文档,数组及文档数组. MongoDB应用场景 大而复杂的数据 移动和社会基础设施数据 内容管理和交付 用户数据 管理数据中心

Centos 7.2中MongoDB数据库的安装与卸载教程

一.前言 大家都知道,MongoDB 是由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统. MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案. MongoDB 将数据存储为一个文档,数据结构由键值(key-value)对组成,其文档类似于 JSON 对象,字段值可以包含其他文档,数组及文档数组.在高负载的情况下,添加更多的节点,可以保证服务器性能. 本文将给大家详细介绍关于在Centos 7.2中MongoDB的安装与卸载,下面话不多说了,来一起看看详细的介

部署Oracle 12c企业版数据库( 安装及使用)

在计算机信息安全领域中,数据库系统无疑有着举足轻重的地位.比如:SQL Server.MySQL.DB2.Oracle等,都是比较知名的数据库管理软件,由于Oracle数据库的安全性,所以在一些比较重要的场合被普遍使用,比如:电信.银行.政府部门等. 本次博文以Oracle 12c数据库的64位企业版为例,学习如何在Centos 操作系统中的安装和基本配置,包括安装的准备等. 博文大纲: 一.安装Oracle 12c 二.创建Oracle 12c数据库 三.Oracle 12c数据库的启动与关闭