-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Yan Lin
committed
Jun 14, 2024
1 parent
3a7bd28
commit b78a725
Showing
13 changed files
with
375 additions
and
3 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
+++ | ||
title = 'FlameGraph火焰图原理' | ||
date = 2022-12-05T10:15:19+08:00 | ||
author = "Skyan" | ||
tags = ["programming", "performance"] | ||
ShowToc = true | ||
ShowBreadCrumbs = true | ||
+++ | ||
|
||
FlameGraph是世界知名计算机性能优化专家Brendan Gregg发明的一种性能数据可视化方法。通过不同色块可交互的展示,可动态展示系统运行时的性能热点。FlameGraph如下图所示: | ||
![flamegraph](flamegraph.png) | ||
|
||
按照作者介绍,FlameGraph可以用于分析CPU耗时,内存分配,非CPU耗时(线程等待,调度性能),混合运行时(CPU和非CPU运行混合),以及性能对比这5种性能分析的可视化。我们常用FlameGraph生成CPU耗时分析图,用来找到服务运行性能的热点,并专项优化以节省资源成本。其实,FlameGraph也有一套标准数据格式,根据这个格式,可以在任何适合的场景中生成所需要的FlameGraph,并可视化可交互地展示和分析。 | ||
|
||
FlameGraph适合用于有调用栈的数据可视化,通过一种类似柱子的布局,加上暖色调的颜色,很像火焰一样的图案,所以被称为火焰图。以大家常用的CPU耗时火焰图为例,它的组成元素如下: | ||
|
||
* 一个函数调用栈用一列方框组成,每个方框代表一个函数 | ||
* y轴代表函数调用的深度,从底向上逐层递进调用,最顶端的函数代表性能采集时刻正在运行的函数,下面是它的父函数,依次类推 | ||
* x轴代表不同采集栈的集合。需要注意的是x轴并不代表时间顺序,同一层的函数按照字母序从左往右排列。这样如果两个同名函数在同一层,将会被合并成一个区块。 | ||
* 方框的宽度代表该函数在调用栈抽样中出现的次数,如果宽度越宽,代表这个函数在栈抽样中出现的越频繁,从CPU耗时的角度来说,也表示这个函数更耗时。 | ||
* 如果方框宽度足够则展示函数的全名,如果不够则展示部分或者不展示,鼠标放上去可以展示全名 | ||
* 每个方框的背景色其实并没有什么特别,其实就是选取了一组随机的暖色调。主要是为了方便眼睛能区分不同层的方框。 | ||
* 一个火焰图可以用于可视化单线程,多线程,多进程甚至多主机的性能数据。也可以为每个线程生成一份独立的火焰图用于更详细的分析。 | ||
* 方框的宽度不仅限于表示抽样次数,也可以表示其他指标。例如宽度可以表示线程阻塞的时长,这样的一个火焰图可以很清晰的看出哪些函数在阻塞线程,以及整个线程阻塞时函数的调用栈情况 | ||
|
||
以上即是一个火焰图静态的组成部分,更为有趣的是,Gregg也为火焰图增加了互动功能,使得用户体验更佳。 | ||
|
||
火焰图本身是一个SVG文件,配合Javascript,支持鼠标悬浮,点击放大,以及搜索功能: | ||
|
||
* 鼠标悬浮:当鼠标光标放置到一个方框上方时,可以展示该方框的详细数据(函数名,采样次数,以及占比) | ||
* 点击放大:当鼠标点击一个方框时,火焰图按照垂直方向放大,该方框以上的函数将被放大展示,其他部分浅色处理,方便聚焦一个函数的分析 | ||
* 搜索:可以用Ctrl/Command+F快捷键或者右上角搜索功能,按照函数名搜索。搜索到的函数会高亮显示,还是在右下角展示所有搜索到的函数出现次数占比 | ||
|
||
火焰图生成流程是怎样的呢?按照Gregg开源的[flamegraph.pl](https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl)程序,生成一个火焰图只需要3步: | ||
|
||
1. 从perf,dtrace,gperftools等程序中获取运行时的调用栈数据 | ||
2. 转换为折叠栈格式(Fold stacks) | ||
3. 调用flamegraph.pl生成火焰图 | ||
|
||
原理非常简单。所以搞懂火焰图只需要明白折叠栈是怎么回事即可。首先一个函数的调用栈可能长这样: | ||
``` | ||
func_c | ||
func_b | ||
func_a | ||
start_thread | ||
clone | ||
``` | ||
|
||
``` | ||
func_d | ||
func_a | ||
start_thread | ||
clone | ||
``` | ||
|
||
``` | ||
func_d | ||
func_a | ||
start_thread | ||
clone | ||
``` | ||
|
||
上面展示了三次性能抽样,每次抽样从底向上显示了一个调用栈,折叠栈格式如下: | ||
``` | ||
clone;start_thread;func_a;func_b;func_c 1 | ||
clone;start_thread;func_a;func_d 2 | ||
``` | ||
|
||
其实就是将三个栈做了一个汇总,将同样的栈汇总到一起,每个函数用';'分隔,最后加空格和打印出现次数。 | ||
|
||
前面还可以加上程序的名称,例如cpp,如下也是一个合法的folded stacks: | ||
``` | ||
cpp;clone;start_thread;func_a;func_b;func_c 1 | ||
cpp;clone;start_thread;func_a;func_d 2 | ||
``` | ||
|
||
有了折叠栈数据,即可利用[flamegraph.pl](https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl)生成对应的火焰图svg文件。该文件可以嵌入到任何网页中展示,简单方便。 | ||
|
||
进一步,我们可以利用火焰图原理,生成各种场景所需要的“火焰图”,用于性能分析,数据可视化展示等功能。我们以应用层算子调度框架的性能分析为例,展示如何利用火焰图生成算子耗时可视化图。 | ||
|
||
应用层算子调度框架常见于RPC服务端的应用层,主要思想是将策略逻辑封装成若干个函数式的算子,再通过调度框架,串行或者并行调度这些算子运行,最终产出RPC的返回结果。算子调度的拓扑图有叫Tree,也有叫DAG的,也有叫图的。下图所示就是一个典型的算子调度框架的运行时拓扑图。 | ||
|
||
![flow](flow.png) | ||
|
||
针对这样的算子调度,经常需要分析的问题有:哪个算子耗时最高,最长路径在哪里,哪个算子可以优化调度路径等,我们通常通过打印日志和统计最长路径等方法来分析运行时性能情况。但同样,通过火焰图也可以将运行拓扑的耗时可视化起来。 | ||
|
||
生成火焰图的关键即为生成栈的汇总数据,假设每个算子的耗时情况如下: | ||
|
||
| 运行阶段 | 解析请求 | 访问A服务 | 访问B服务 | 汇总结果 | 产生返回数据 | | ||
| --- | --- | --- | --- | --- | --- | | ||
| 开始运行时间 | 0 | 10 | 10 | 24 | 32 | | ||
| 结束运行时间 | 10 | 20 | 24 | 32 | 34 | | ||
|
||
我们想象有一个抽样程序,可以按照上面的运行时图,从左往右分别垂直抽样,可以看出在不同时间段有这样几种“栈”: | ||
|
||
| | 栈1 | 栈2 | 栈3 | 栈4 | 栈5 | | ||
| --- | --- | --- | --- | --- | --- | | ||
| 阶段 | 解析请求 | 访问A服务 | 访问B服务 | 访问B服务 | 汇总结果 | 产生返回数据 | | ||
| 耗时 | 10 | 10 | 4 | 8 | 2 | | ||
|
||
显然栈2的“访问A服务”和“访问B服务”并不是父子调用关系,而是一种并行关系,但这个不影响火焰图的解读。可以显然从上面的栈中可以观察出,访问B服务耗时更久。 | ||
|
||
所以这种场景下,火焰图的垂直方向代表了一种并行运行的关系,上下层之间不存在父子关系,而是并行调度关系。通过将若干次算子调度数据的抽样结果汇总到一起,每个折叠栈后面的数字代表该栈的总耗时,我们可以得到类似下面的折叠栈数据: | ||
``` | ||
dag;解析请求 10 | ||
dag;访问B服务;访问A服务 10 | ||
dag;访问B服务 4 | ||
dag;汇总结果 8 | ||
dag;产生返回数据 2 | ||
``` | ||
|
||
生成的火焰图如下所示: | ||
![flamegraph1](flamegraph1.png) | ||
|
||
从这张图中可以很方便地分析出: | ||
|
||
1. 最长路径是产生返回数据-汇总结果-解析请求-访问B服务,而访问A服务是一个分支路径,虽然也产生耗时但不在最长路径上 | ||
2. 每层每个算子的耗时占比一目了然 | ||
3. 并行算子运行情况也直接展现,可以用于分析哪些并行是合理的,哪些还可以做并行化优化 | ||
|
||
当然,火焰图也会对用户造成顺序的困扰,原因是每层的排序是按照字母序,如果可以进一步改进采用算子拓扑序作为每层的排序标准,整个结果的解读将更加方便。 | ||
|
||
那么问题又来了,我们如何产生算子调度框架的折叠栈数据呢?其实原理非常简单,我们只需要采集每个算子的开始运行时间和运行时长,汇总起来做一个转换就可以得到折叠栈数据。具体转换算法不再详述。 | ||
|
||
从上面的例子可以看出,一旦搞懂火焰图的原理,我们就可以将火焰图应用在各种合适的场景。无论是算子的调度框架性能分析,还是分布式微服务最长路径的可视化分析,还是内存占用分析,还是内存泄漏定位,还是分布式集群资源调度的性能分析,都可以利用火焰图可视化的方法来分析和优化,有效地提升了生产效率。 | ||
|
||
总结一下,火焰图是一个强大的工具,目前已经被广泛应用于性能分析优化领域。我们可以在更多实际场景中扩展应用火焰图原理的范围,生成满足我们可视化分析需求的火焰图,用于自动化和可视化的高效分析,提升性能优化的效率。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
+++ | ||
title = 'Memory Models读后感' | ||
date = 2022-10-09T10:15:19+08:00 | ||
author = "Skyan" | ||
tags = ["programming"] | ||
ShowToc = true | ||
ShowBreadCrumbs = true | ||
+++ | ||
|
||
读了Russ Cox关于Memory Models的三篇综述文章: | ||
* [Hardware Memory Models](https://research.swtch.com/hwmm) | ||
* [Programming Language Memory Models](https://research.swtch.com/plmm) | ||
* [Updating the Go Memory Model](https://research.swtch.com/gomm) | ||
|
||
读完之后终于搞明白Memory Models的大体脉络。 | ||
|
||
首先在一个单核单线程的程序中,是没有内存模型问题的。随着多核多线程的引入,程序开始实现并行化提升性能,同时编译器、操作系统以及硬件针对并行化做了大量的优化,这就出现了很多新的并发数据访问问题,就需要设计规范的内存模型来解决。 | ||
|
||
内存模型分为硬件内存模型和编程语言内存模型两个层面。 | ||
|
||
其中硬件内存模型分为以Intel CPU为代表的x86-TSO模型和以ARM/POWER为代表的Relaxed Memory Model模型。前者模型中,多核对写入主存的数据提供全局序,后者模型中,多核之间不存在全局序的概念。为了协调不同硬件,又有DRF-SC(Data-Race-Free Sequential Consistency)模型,该模型是一种能让大部分硬件都能接受实现的顺序内存访问模型,这样DRF-SC可以作为硬件内存模型的统一标准。 | ||
|
||
编程语言内存模型的定义分为两个流派: | ||
|
||
1. 以Java,Javascript为代表的happen-before流派。特点是通过happen-before语义来定义语言的哪些方面如何实现数据的同步。该流派规范定义最早,也最为经典。Java9以后增加了弱同步,类似C++的弱同步(acquire/release)。 | ||
2. 以C/C++,Rust,Swift等为代表的强-弱-无顺序流派。特点是分为三类同步语义,强代表前后顺序一致,和Java的happen before语义一致。弱代表在部分协调条件下的一致。无代表没有同步。 | ||
|
||
按照作者的介绍,这两个流派的内存模型都存在缺陷。Java的内存模型定义并不严格,还是有非因果、非一致的情况发生。C++的内存模型有很多未定义的情况,尤其是弱同步和无同步语义其实存在很多坑,甚至在不同硬件下运行结果可能还不一致。 | ||
|
||
Go语言采取中间流派,即只通过happen-before来定义同步语义,只支持顺序一致,不支持弱同步,不支持无同步,严格定义,同时约束编译器的优化,保证最终结果可预测,即使有未定义的情况发生,也能约束结果在有限的范围内。 | ||
|
||
最终结论是,截止目前2022年,内存模型还处于不断探索和研究的方向,Go的这个尝试也是建立在无数前人理论的基础之上,未来有待更加规范定义的模型出现。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters