Go语言性能优化与优化实例(体系)

本文深入探讨了Go语言的内存管理,包括自动内存管理机制、内存分配优化如BalancedGC,以及编译器优化,如函数内联和逃逸分析。通过这些优化,可以提升软件系统的性能,减少内存消耗,并提高用户体验。同时,文章还介绍了编译器的结构和静态分析在性能优化中的作用。
摘要由CSDN通过智能技术生成

Go语言优化与落地实践

内存管理优化

编译器优化

自动内存管理和Go内存管理机制

编译器优化的基本问题和思路

性能问题和优化方案

性能优化是什么

提升软件系统处理能力,减少不必要的消耗,充分发掘计算机的算力

为什么要做性能优化

用户体验: 带来体验的提升

资源高效利用 :降低 成本,提高效率

性能优化的层面

  • 业务代码 针对特定场景,具体问题,具体分析,容易获得较大性能收益

  • SDK

  • 基础库

  • 语言运行时 (GC 调度器) 解决更通用的性能问题 考虑更多场景

  • OS 数据驱动优化 自动化功能分析工具 pprof

依靠数据而非猜测 首选优化是最大瓶颈

软件质量至关重要

测试用例:覆盖尽可能多的场景,方便回归 文档 隔离 可观测:必要的日志输出

自动内存管理

什么是自动内存管理

  • 动态内存

  • 程序在运行是根据需求动态分配的内存 malloc()

  • 自动内存回收:由程序语言的运行时系统管理动态内存

  • 避免手动管理内存,专注实现业务逻辑

  • 保证内存使用的正确性和安全性 double-free problem user-after-free problem 有很多问题

  • 三个任务

    • 为新对象分配空间

    • 找到存活对象

    • 回收死亡对象的内存空间

    自动内存管理 相关概念

    • Mutator 业务线程,分配新对象,修改对象指向关系

    • Collector GC线程,找到存活对象,回收死亡对象的内存空间

    • Serial GC 只有一个Collector

    • Parallel GC 支持多个collectors同时回收的GC算法

    • Concurrent GC mutator(s)和collector(s)可以同时进行

    评价GC算法指标

    • 安全性 不能回收存活的对象

    • 吞吐率 花在GC上的时间

    • 暂停时间 stop the world (STW) 业务是否感知

    • 内存开销 GC元数据开销

      追踪垃圾回收

      • 对象被回收的条件::指向关系不可达的对象

      • 标记根对象

        • 静态变量,全局变量,常量,线程栈等

      • 标记:找到可达对象

        • 求指针指向的传递闭包:从根对象出发,找到多于哦可达对象

      • 清理:所有不可达对象

        • 将存活的对象复制到另外的内存空间(Copying GC)

        • 将死亡对象的内存标记为“可分配”(Mark-sweep GC)

        • 移动并整理存活对象(Mark-compact GC)

    • 引用计数

      • 根据对象的声明周期,使用不同的标记和清理策略

      分代GC

      • 分代假说 : most objects die young

      • Intuition 很多对象在分配出来后很块就不再使用了

      • 每个对象都有年龄:经历过GC的次数

      • 目的:对年轻和老年的对象,指定不同的GC策略,降低整体内存管理的开销

      • 不同年龄的对象处于heap的不同区域

      • 老年代

        • 常规的对象分配

        • 由于存活对象很少,可以采用copying collection

        • GC 吞吐率很高

      • 老年代

        • 对象趋向与一直或者,反反复复开销较大

        • 可以采用mark-sweep collection

    引用计数

    • 每个对象都有一个与之关联的引用数目

    • 对象存活的调教:当且仅当引用数大于0

    • 优点

      • 内存管理的操作被平摊到程序执行过程中

      • 内存管理不需要了解runtime的实现细节,C++只能指针(smart pointer)

    • 缺点

      • 维护引用技术的开销较大,通过原子操作保证对引用技术操作的原子性和可见性

      • 无法回收环形数据结构 --- weak reference

      • 内存开销 每个对象都引入的额外内存空间存储引用数目

      • 回收内存时依然可能引发暂停

Go内存管理

  • 目标:为对象在heap上分配内存

  • 提前将内存分块

    • 调用系统调用mmap( ),向OS申请一大块内存

    • 先将内存划分成大块 mspan

    • 再将大块继续划分成特定大小的小块,用于对象分配

    • noscan mspan :分配不包含指针的对象,GC不要扫描

    • scan mspan 分配包含指针的对象--GC需要扫描

  • 对象分配:根据对象的大小,选择最合适的块返回

Go内存分配 - 缓存

  • TCMalloc:thread caching

  • 每个p包含一个mcache用于快速分配,用于为绑定于p上的g分配对象

  • mache管理一组mspan

  • 当mcache中的mspan分配完毕,向mcentral申请待有未分配块的mspan

  • 当mspan中没有分配的对象,mspan会被缓存在mcentral中,而不是立刻释放并规划给OS

Go内存分配 - 分块

  • 目标 为对象在heap上分配内存

  • 提前将内存分块

    • 调用系统调用mmap()向OS申请一大块内存

    • 先将内存划分成大块

    • 再将大块继续划分成特定大小的小块,用于对象分配

    • noscan mspan分配不包含指针的对象--GCbuxuyao1saomiao1

    • scan mspan分配包含指针的对象--GC需要扫描

  • 对象分配:根绝对象的大小,选择最合适的块返回

Go内存管理优化

  • 对象分配是非常高频的操作:每秒分配GB级别的内存

  • 小对象占比高

  • Go内存分配耗时

    • 分配路径长:g->m->p>mache->memory block->return poniter

    • pprof 对象分配的函数是最频繁调用的函数之一

优化方案 Balanced GC

  • 每个g都绑定一大块内存

  • GAB用于noscan类型的小对象分配

  • 使用三个指针维护GAB:base,end,stop

  • Bump poinyer(指针碰撞) 风格对象分配

    • 无需和其他分配请求互斥

    • 分配动作简单高效

    Balanced GC

    • GAB对于Go内存管理来说是一个大对象

    • 本质:将多个小对象的分配合并成一次大对象的分配

    • 问题:GAB的对象分配方式会导致内存被延迟释放

    • 方案:移动GAB中存活的对象

      • 当GAB总大小超过一定阈值时,将GAB中存活的对象复制到另外分配的GAB中

      • 原先的GAB可以释放,避免内存泄露

      • 本质:用copying GC 的算法管理小对象

      • 根据对象的声明周期,使用不同的标记和清理策略

编译器和静态分析

编译器的结构

  • 作用 重要的系统软件

    • 识别符合语法和非法的程序‘

    • 生成正确且高效的代码

  • 分析部分 (前端 front end)

    • 词法分析 生成词素

    • 词法分析 生成语法树

    • 语义分析,收集类型信息,进行语义检查

    • 中间代码生成,声成intermediate representation(IR)

  • 综合部分(后端 back end)

    • 代码生成,机器无关优化,生成优化的IR

    • 代码生成,生成目标代码

    •  

静态分析

  • 静态分析:不执行程序代码,推到程序的行为,分析程序的性质

  • 控制流(Control flow):程序执行的流程

  • 数据流(Data flow):数据在控制流上的传递

  • 通过分析数据流和控制流,我们可以知道更多关于程序的性质

  • 根据这些性质优化代码

  • 过程内分析:仅在函数内部进行分析

  • 过程间分析:考虑函数调用时参数传递和返回值的数据流和控制流(联合求解,比较复杂)

Go编译器优化

  • 为什么做编译器优化

    • 用户无感知,重新编译即可重新获得性能收益

    • 通用化优化

  • 现状

    • 采用的优化少

    • 编译时间较短,没有进行较复杂的代码分析和优化

  • 优化编译的思路

    • 场景 面向后端长期执行任务

    • Tradoff:用编译时间换取更高效的机器码

  • Beast mode

    • 函数内联

      • 将被调用函数的函数体(callee)的副本替换到调用位置上(caller)上,同时重写代码以反映参数的绑定

      • 优点

        • 消除函数调用开销

        • 将过程间分析转化为过程内分析,帮助其他优化,例如逃逸分析

      • 缺点

        • 影响性能

        • 函数体变大

        • 编译生成的Go镜像变大

      • 函数内联在大多数情况下是正向优化

      • Go函数内联受到的限制较多

        • 语言特性

        • 内联策略非常保守

      • Beast mode:调整函数内联的策略,使更多函数被内联

        • 降低函数调用的开销

        • 增加了其他优化的机会:逃逸分析

    • 逃逸分析

      • 逃逸分析 分析代码中指针的动态作用域:指针在何处可以被访问

      • 大致思路

        • 从对象分配处出发,沿着控制流,观察对象的数据、* 若发现指针P在当前作用域s:

          1,作为参数传递给其他函数

          2,传递给全局变量

          3,传递给其他的goroutine

          4,传递给已逃逸的指针指向的对象

          则指针p指向的对像逃逸处s,反之则没有逃逸处

        • Beast mode 函数内联拓展了函数边界,反之则没有逃逸处s

        • 优化

          • 未逃逸的对象可以在栈上分配

          • 对象在栈上分配和回收很快

          • 减少在heap上的分配,降低GC负担

    • 默认栈大小调整

    • 边界检查消除

    • 循环展开

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值