当前位置: 首页 > news >正文

Go应用性能分析实战

Go很适合用来开发高性能网络应用,但仍然需要借助有效的工具进行性能分析,优化代码逻辑。本文介绍了如何通过go test benchmark和pprof进行性能分析,从而实现最优的代码效能。原文: Profiling Go Applications in the Right Way with Examples[1]

Go Gopher为"A Journey With Go"创作的插图,作者Renee French
Go Gopher为"A Journey With Go"创作的插图,作者Renee French
什么是性能分析?

性能分析(Profiling) 是分析应用程序从而识别阻碍应用性能的瓶颈的基本技术,有助于检测代码的哪些部分执行时间太长消耗太多资源(如CPU和内存)。

分析方法

有三种分析方法。

  • Go test(包含基准测试)
  • 基于 runtime/pprof [2]运行时分析
  • 基于 net/http/pprof [3]Web分析
分析类型
  • CPU (收集应用程序CPU使用情况的数据)
  • 堆(Heap)/内存(Memory) (收集应用程序内存使用情况的数据)
  • Goroutine (识别创建最多Goroutine的函数)
  • 阻塞 (识别阻塞最多的函数)
  • 线程 (识别创建线程最多的函数)
  • 互斥锁 (识别有最多锁竞争[4]的函数)

本文将主要关注使用上述方法进行CPU和内存分析。

1. 基准测试(Benchmarking)

我想实现著名的两数之和算法[5],这里不关注实现细节,直接运行:

go test -bench=.

-bench参数运行项目中的所有基准测试。

go test bench输出
go test bench输出

根据上面的输出,与其他方法相比,TwoSumWithBruteForce是最有效的方法。别忘了结果取决于函数输入,如果输入一个大数组,会得到不同的结果。😅

如果输入go help testflag,将看到许多参数及其解释,比如countbenchtime等,后面将解释最常用的参数。

  • 如果要运行特定函数,可以通过如下方式指定:
go test -bench='BenchmarkTwoSumWithBruteForce'
  • 默认情况下,基准测试函数只运行一次。如果要自定义,可以使用 count参数。例如,
go test -bench='.' -count=2

输出如下所示。

带count参数的基准测试输出
带count参数的基准测试输出
  • 默认情况下,Go决定每个基准测试操作的运行时间,可以通过自定义 benchtime='2s'指定。

可以同时使用countbenchtime参数,以便更好的度量基准函数。请参考How to write benchmarks in Go[6]

示例代码请参考Github[7]

在现实世界中,函数可能既复杂又长,计时毫无作用,因此需要提取CPU和内存分析文件以进行进一步分析。可以输入

go test -bench='.' -cpuprofile='cpu.prof' -memprofile='mem.prof'

然后通过pprof[8]工具对其进行分析。

1.1 CPU分析

如果输入

go tool pprof cpu.prof

并回车,就会看到pprof交互式控制台。

go tool pprof cpu.prof输出
go tool pprof cpu.prof输出

我们来看看最主要的内容。

  • 输入 top15查看执行期间排名前15的资源密集型函数。 (15表示显示的节点数。)
alt

为了解释清楚,假设有一个A函数。

func A() {
   B()             // 耗时1s
   DO STH DIRECTLY // 耗时4s
   C()             // 耗时6s
}

flat值和cum值计算为: flat值为A=4, cum值为A=11(1s + 4s + 6s)

  • 如果要基于cum进行排序,可以键入 top15 -cum。也可以分别使用 sort=cumtop15命令。
  • 如果通过 top获得更详细的输出,可以指定 granularity选项。例如,如果设置 granularity=lines,将显示函数的行。
粒度为行的top15输出
粒度为行的top15输出

得益于此,我们可以识别导致性能问题的函数的特定行。😌

  • 输出还显示了运行时函数和用户自定义函数。如果只想关注自己的函数,可以设置 hide=runtime并再次执行 top15
带hide选项的top15
带hide选项的top15

可以通过输入hide=来重置。

  • 此外,可以使用 show命令。例如,输入 show=TwoSum
alt
  • 如果只关注指定节点,可以使用 focus命令。例如关注 TwoSumOnePassHashTable,显示为
同时带hide和focus的top输出
同时带hide和focus的top输出

可以输入focus=来重置。

  • 如果需要获取该功能的详细信息,可以使用 list命令。例如,想获得关于 TwoSumWithTwoPassHashTable函数的详细信息,输入 list TwoSumWithTwoPassHashTable
list TwoSumWithTwoPassHashTable输出
list TwoSumWithTwoPassHashTable输出
  • 如果要查看图形化的调用栈,可以键入 web
web输出
web输出
node细节
node细节

后面将提供更多关于分析图表的细节。

  • 还可以键入 gifpdf以与他人共享相应格式的分析数据。😃
1.2 内存分析

如果输入go tool pprof mem.prof并回车

go tool pprof mem.prof输出
go tool pprof mem.prof输出
top10输出
top10输出

注意,上面提到的flat和cum是相同的东西,只是测量不同的东西(CPU单位ms,内存单位MB)。

  • list命令
list TwoSumWithBruteForce输出
list TwoSumWithBruteForce输出
  • web命令
web命令输出
web命令输出

可以使用CPU分析部分中提到的所有命令。

下面看一下另一个方法,runtime/pprof。🚀

2. 基于runtime/pprof[9]的运行时分析

基准测试对单个函数的性能很有用,但不足以理解整体情况,这时就需要用到runtime/pprof💠。

2.1 CPU分析

基准测试内置CPU和内存分析,但如果需要让应用程序支持运行时CPU分析,必须首先显示启用。

alt

如果执行go run .,将看到生成的cpu.prof文件,可以通过基准测试部分提到的go tool pprof cpu.prof对齐进行分析。

本节将介绍我最喜欢的特性之一pprof.Labels此特性仅适用于CPU和goroutine分析[10]

如果要向特定函数添加一个或多个标签,可以使用pprof.Do函数。

pprof.Do(ctx, pprof.Labels("label-key""label-value"), func(ctx context.Context) {
    // 执行标签代码
})

例如,

向特定函数添加标签
向特定函数添加标签

在pprof交互式控制台中,键入tags,将显示带了有用信息的标记函数。

tags输出
tags输出

可以用标签做很多事情[11],阅读Profiler labels in Go[12]可以获得更多信息。

pprof还有很棒的web界面,允许我们使用各种可视化方式分析数据。

输入go tool pprof -http=:6060 cpu.proflocalhost:6060将被打开。 (为了更清楚,我去掉了pprof.Labels)

让我们深入探讨图形表示。

CPU分析图
CPU分析图

节点颜色、字体大小、边缘粗细等都有不同含义,参考pprof: Interpreting the Callgraph[13]获取更多细节。可视化使我们能够更容易识别和修复性能问题。

单击图中的节点,可以对其进行细化,我们可以根据自己的选择对可视化进行过滤。下面展示了部分内容(focushide等)。

Refine选项
Refine选项

还可以看到其他可视化选项。

View选项
View选项

上面出现了peek和source(作为list命令),因此下面将介绍火焰图(Flame Graph)[14]。火焰图提供了代码时间花费的高级视图。

火焰图
火焰图

每个函数都用一个彩色矩形表示,矩形的宽度与该函数花费的时间成正比。

alt

可以访问Github[15]获取源码。

2.2 内存分析

如果需要向应用程序添加运行时内存分析,必须显式启用。

可以访问Github[16]获取源码。

alt

如果执行go run .,将看到生成的mem.prof文件,可以用之前基准测试部分提到的go tool pprof mem.prof对齐进行分析。

下面将介绍两个更有用的命令treepeek

  • tree显示了执行流的所有调用者和被调用者。
tree输出
tree输出

从而帮助我们识别执行流并找出消耗最多内存的对象。 (不要忘记使用granularity=lines,它提供了更可读的格式。)

  • 如果希望查看特定函数的执行流程,可以使用 peek命令。例如, peek expensiveFunc显示如下
alt
  • 还可以使用pprof web界面进行内存分析。输入 go tool pprof -http=:6060 mem.prof,打开 localhost:6060
内存分析图
内存分析图
3. 基于net/http/pprof[17]的Web分析

runtime/pprof包提供了Go程序性能分析的低级接口,而net/http/pprof为分析提供了更高级的接口,允许我们通过HTTP💃收集程序分析信息,所需要做的就是:

添加net HTTP pprof
添加net HTTP pprof

输入localhost:5555/debug/pprof,就能在浏览器上看到所有可用的分析文件。如果没有使用stdlib,可以查看fiber[18]gin[19]echo[20]的pprof实现。

debug/pprof视图
debug/pprof视图

文档里记录了所有用法和参数[21],我们看一下最常用的。

获取CPU分析数据及技巧
go tool pprof http://localhost:5555/debug/pprof/profile?seconds=30

在CPU分析期间,请注意

runtime.mallogc → 表示可以优化小堆分配的数量。

syscall.Read或者syscall.Write → 表示应用程序在内核模式下花费了大量时间,为此可以尝试I/O缓冲。

获取堆(采样活跃对象内存分配)分析数据及技巧
go tool pprof http://localhost:5555/debug/pprof/heap

go tool pprof http://localhost:5555/debug/pprof/heap?gc=1

就我个人而言,我喜欢用GC参数诊断问题。例如,如果应用程序有内存泄漏问题,可以执行以下操作:

  • 触发GC( 浏览器访问/debug/pprof/heap?gc=1)
  • 下载堆数据, 假设下载文件名为file1
  • 等待几秒或几分钟
  • 再次触发GC( 浏览器访问/debug/pprof/heap?gc=1)
  • 再次下载堆数据, 假设下载文件名为file2
  • 使用 diff_base [22]进行比较
go tool pprof -http=:6060 -diff_base file2 file1
diff_base输出
diff_base输出
获取内存分配(抽样过去所有的内存分配)分析数据及技巧
go tool pprof http://localhost:5555/debug/pprof/allocs

在内存分配分析期间,可以这样做

  • 如果看到 bytes.growSlice,应该考虑使用 sync.Pool
  • 如果看到自定义函数,请检查是否在切片或映射中定义了固定容量。
延伸阅读
  1. pprof Github Readme [23]
  2. Profiling Go Programs by Russ Cox [24]
  3. pprof man page [25]
  4. GopherCon 2019: Dave Cheney — Two Go Programs, Three Different Profiling Techniques [26]
  5. GopherCon 2021: Felix Geisendörfer — Go Profiling and Observability from Scratch [27]
  6. GopherConAU 2019 — Alexander Else — Profiling a go service in production [28]
  7. Practical Go Lessons Profiling Chapter [29]

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

Profiling Go Applications in the Right Way with Examples: https://blog.stackademic.com/profiling-go-applications-in-the-right-way-with-examples-e784526e9481

[2]

runtime/pprof: https://pkg.go.dev/runtime/pprof

[3]

net/http/pprof: https://pkg.go.dev/net/http/pprof

[4]

Resource Contention: https://en.wikipedia.org/wiki/Resource_contention

[5]

Two Sum Algorithm: https://leetcode.com/problems/two-sum

[6]

How to write benchmarks in Go: https://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go

[7]

pprof-example: https://github.com/Abdulsametileri/pprof-examples/tree/main/benchmarking

[8]

pprof: https://linux.die.net/man/1/pprof

[9]

runtime/pprof: https://pkg.go.dev/runtime/pprof

[10]

pprof.Labels: https://pkg.go.dev/runtime/pprof#Labels

[11]

pprof tags: https://github.com/google/pprof/blob/main/doc/README.md#tags

[12]

Profiler labels in Go: https://rakyll.org/profiler-labels

[13]

pprof: Interpreting the Callgraph: https://github.com/google/pprof/blob/main/doc/README.md#interpreting-the-callgraph

[14]

火焰图(Flame Graph): https://github.com/google/pprof/blob/main/doc/README.md#flame-graph

[15]

runtime pprof cpu example: https://github.com/Abdulsametileri/pprof-examples/tree/main/runtimepprof/cpu

[16]

runtime pprof memory example: https://github.com/Abdulsametileri/pprof-examples/tree/main/runtimepprof/mem

[17]

net/http/pprof: https://pkg.go.dev/net/http/pprof

[18]

fiber pprof: https://docs.gofiber.io/api/middleware/pprof

[19]

gin pprof: https://github.com/gin-contrib/pprof

[20]

echo pprof: https://pkg.go.dev/github.com/labstack/echo-contrib/pprof

[21]

net/http/pprof usage examples: https://pkg.go.dev/net/http/pprof#hdr-Usage_examples

[22]

pprof comparing profiles: https://github.com/google/pprof/blob/main/doc/README.md#comparing-profiles

[23]

pprof Github Readme: https://github.com/google/pprof/blob/main/doc/README.md

[24]

Profiling Go Programs by Russ Cox: https://blog.golang.org/2011/06/profiling-go-programs.html

[25]

pprof man page: https://linux.die.net/man/1/pprof

[26]

GopherCon 2019: Dave Cheney — Two Go Programs, Three Different Profiling Techniques: https://www.youtube.com/watch?v=nok0aYiGiYA

[27]

GopherCon 2021: Felix Geisendörfer — Go Profiling and Observability from Scratch: https://www.youtube.com/watch?v=7hg4T2Qqowk

[28]

GopherConAU 2019 — Alexander Else — Profiling a go service in production: https://www.youtube.com/watch?v=19bxBMPOlyA

[29]

Practical Go Lessons Profiling Chapter: https://www.practical-go-lessons.com/chap-36-program-profiling

本文由 mdnice 多平台发布

相关文章:

Go应用性能分析实战

Go很适合用来开发高性能网络应用,但仍然需要借助有效的工具进行性能分析,优化代码逻辑。本文介绍了如何通过go test benchmark和pprof进行性能分析,从而实现最优的代码效能。原文: Profiling Go Applications in the Right Way with Examples…...

MySQL的索引类型

目录 1. 主键索引 (PRIMARY KEY) 2. 唯一索引 (UNIQUE) 3. 普通索引 (INDEX) 4. 全文索引 (FULLTEXT) 5. 空间索引 (SPATIAL) 6. 组合索引 (COMPOSITE INDEX) 7. 前缀索引 (PREFIX INDEX) 8. 覆盖索引 (COVERING INDEX) 1. 主键索引 (PRIMARY KEY) 描述:表…...

picker选择器-年月日选择

从底部弹起的滚动选择器。支持五种选择器,通过mode来区分,分别是普通选择器,多列选择器,时间选择器,日期选择器,省市区选择器,默认是普通选择器。 学习一下日期选择器 平台差异说明 日期选择默…...

【LeetCode-494】目标和(回溯动归)

目录 LeetCode494.目标和 题目描述 解法1:回溯法 代码实现 解法2:动态规划 代码实现 LeetCode494.目标和 题目链接 题目描述 给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 和 -。对于数组中…...

力扣 188. 买卖股票的最佳时机 IV

题目来源:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/description/ C题解:动态规划 思路同力扣 123. 买卖股票的最佳时机 III-CSDN博客,只是把最高2次换成k次。如果思路不清晰,可以将k从0写到4等找找规律…...

【Go语言】Go项目工程管理

GO 项目工程管理(Go Modules) Go 1.11 版本开始,官方提供了 Go Modules 进行项目管理,Go 1.13开始,Go项目默认使用 Go Modules 进行项目管理。 使用 Go Modules的好处是不再需要依赖 GOPATH,可以在任意位…...

美容小程序:让预约更简单,服务更贴心

在当今繁忙的生活节奏中,美容预约常常令人感到繁琐和疲惫。为了解决这个问题,许多美容院和SPA中心已经开始采用美容小程序来简化预约流程,并提供更加贴心的服务。在这篇文章中,我们将引导您了解如何制作一个美容小程序&#xff0c…...

【递归】:原理、应用与案例解析 ,助你深入理解递归核心思想

递归 1.基础简介 递归在计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集 例如 递归遍历环形链表 基本情况(Base Case):基本情况是递归函数中最简单的情况,它们通常是递…...

【 Maven 】花式玩法之多模块项目

目录 一、认识Maven多模块项目 二、maven如何定义项目的发布策略 2.1 版本管理 2.2 构建配置 2.3 部署和发布 2.4 依赖管理 2.5 发布流程 三、使用Jenkins持续集成Maven项目 四、总结 如果你有一个多模块项目,并且想将这些模块发布到不同的仓库或目标位置&…...

LeetCode 热题 100 Day01

哈希模块 哈希结构: 哈希结构,即hash table,哈希表|散列表结构。 图摘自《代码随想录》 哈希表本质上表示的元素和索引的一种映射关系。 若查找某个数组中第n个元素,有两种方法: 1.从头遍历,复杂度&#xf…...

[vscode]vue js部分结尾加分号

设置中寻找 semicolons确定在TypeScript的这个扩展中设置选项为insert...

友点CMS image_upload.php 文件上传漏洞复现

0x01 产品简介 友点CMS是一款高效且灵活的网站管理系统,它为用户提供了简单易用的界面和丰富的功能。无论是企业还是个人,都能通过友点CMS快速搭建出专业且美观的网站。该系统支持多种内容类型和自定义模板,方便用户按需调整。同时,它具备强大的SEO功能,能提升网站在搜索…...

C语言—指针(3)

嘿嘿嘿嘿,你看我像指针吗? 不会写,等我啥时候会写了再说吧,真的累了,倦了 1.面试题 1)定义整形变量i; 2)p为指向整形变量的指针变量; 3)定…...

【八股文】面向对象基础

【八股文】面向对象基础 面向对象和面向过程的区别 面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。面向对象会先抽象出对象,然后用对象执行方法的方式解决问题。 创建一个对象用什么运算符?对象实体与对象引用有何不同? …...

Day49 647 回文子串 516 最长回文子序列

647 回文子串 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 方法一:动态规划: 采用一个二维的dp数组&#xf…...

探秘GNU/Linux Shell:命令行的魔法世界

GNU/Linux的Shell是一种特殊的交互式工具,为用户提供了强大的控制和管理Linux系统的方式。在这个博客中,我们将深入了解Shell的基本概念、功能以及不同类型的Shell。 Shell的本质 Shell的核心是命令行提示符,它是用户与Linux系统进行交互的…...

基于STM32F407的coreJSON使用教程

目录 概述 工程建立 代码集成 函数介绍 使用示例 概述 coreJSON是FreeRTOS中的一个组件库,支持key查找的解析器,他只是一个解析器,不能生成json数据。同时严格执行 ECMA-404 JSON 标准。该库用 C 语言编写,设计符合 ISO C90…...

keepalived双主模式测试

文章目录 环境准备部署安装keepavlived配置启动测试模拟Nginx宕机重新启动问题分析 环境准备 测试一下keepalived的双主模式,所谓双主模式就是两个keepavlied节点各持有一个/组虚IP,默认情况下,二者互为主备,同时对外提供服务&am…...

微服务中的熔断、降级和限流

在现代微服务架构中,熔断、降级和限流是保障系统稳定性和可靠性的重要手段。本文将深入探讨这三种机制在微服务架构中的作用、原理以及实践方法。 1. 熔断(Circuit Breaker) 1.1 作用和原理 熔断器是一种可以在服务发生故障时快速中断请求的机制,防止故障蔓延到整个系统…...

2023年便宜的云服务器分享:最低26元4核16G

2024年阿里云服务器租用价格表更新,云服务器ECS经济型e实例2核2G、3M固定带宽99元一年、ECS u1实例2核4G、5M固定带宽、80G ESSD Entry盘优惠价格199元一年,轻量应用服务器2核2G3M带宽轻量服务器一年61元、2核4G4M带宽轻量服务器一年165元12个月、2核4G服…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...

深入浅出WebGL:在浏览器中解锁3D世界的魔法钥匙

WebGL&#xff1a;在浏览器中解锁3D世界的魔法钥匙 引言&#xff1a;网页的边界正在消失 在数字化浪潮的推动下&#xff0c;网页早已不再是静态信息的展示窗口。如今&#xff0c;我们可以在浏览器中体验逼真的3D游戏、交互式数据可视化、虚拟实验室&#xff0c;甚至沉浸式的V…...

Python爬虫(52)Scrapy-Redis分布式爬虫架构实战:IP代理池深度集成与跨地域数据采集

目录 一、引言&#xff1a;当爬虫遭遇"地域封锁"二、背景解析&#xff1a;分布式爬虫的两大技术挑战1. 传统Scrapy架构的局限性2. 地域限制的三种典型表现 三、架构设计&#xff1a;Scrapy-Redis 代理池的协同机制1. 分布式架构拓扑图2. 核心组件协同流程 四、技术实…...

大模型智能体核心技术:CoT与ReAct深度解析

**导读&#xff1a;**在当今AI技术快速发展的背景下&#xff0c;大模型的推理能力和可解释性成为业界关注的焦点。本文深入解析了两项核心技术&#xff1a;CoT&#xff08;思维链&#xff09;和ReAct&#xff08;推理与行动&#xff09;&#xff0c;这两种方法正在重新定义大模…...

OCC笔记:TDF_Label中有多个相同类型属性

注&#xff1a;OCCT版本&#xff1a;7.9.1 TDF_Label中有多个相同类型的属性的方案 OCAF imposes the restriction that only one attribute type may be allocated to one label. It is necessary to take into account the design of the application data tree. For exampl…...

C#学习12——预处理

一、预处理指令&#xff1a; 解释&#xff1a;是在编译前由预处理器执行的命令&#xff0c;用于控制编译过程。这些命令以 # 开头&#xff0c;每行只能有一个预处理指令&#xff0c;且不能包含在方法或类中。 个人理解&#xff1a;就是游戏里面的备战阶段&#xff08;不同对局…...

C++信息学竞赛中常用函数的一般用法

在C 信息学竞赛中&#xff0c;有许多常用函数能大幅提升编程效率。下面为你介绍一些常见函数及其一般用法&#xff1a; 一、比较函数 1、max()//求出a&#xff0c;b的较大值 int a10,b5,c;cmax(a,b);//得出的结果就是c等于10. 2、min()//求出a&#xff0c;b的较小值 int a1…...

【立体匹配】:双目立体匹配SGBM:(1)运行

注&#xff1a;这是一个专题&#xff0c;我会一步步介绍SGBM的实现&#xff0c;按照我的使用和优化过程逐步改善算法&#xff0c;附带实现方法 系列文章【立体匹配】&#xff1a;双目立体匹配SGBM&#xff1a;&#xff08;1&#xff09;运行 【立体匹配】&#xff1a;双目立体匹…...

SQL 注入开放与修复

开发&#xff1a; SQL 注入是一种数据库攻击手段。攻击者通过向应用程序提交恶意代码来改变原 SQL 语句的含义&#xff0c; 进而执行任意 SQL 命令&#xff0c;达到入侵数据库乃至操作系统的目的。 例如&#xff1a;下面代码片段中&#xff0c;动态构造并执行了一个 SQ…...