译 | Profiling Go Programs(二)

简介: 译 | Profiling Go Programs(二)

我们可以一目了然看到该程序将大部分时间花在哈希操作上,这与使用Go的 map 值相对应。 我们让 web 只使用包含特定函数的样本,例如 runtime.mapaccess1_fast64 ,可以清除图中的一些噪音:

(pprof) web mapaccess1

如果斜视,我们可以看到 main.FindLoopsmain.DFSruntime.mapaccess1_fast64 的调用。

现在我们已经大致了解了大局,现在是时候放大一个特定的函数了。 让我们先看看 main.DFS ,因为它是一个较短的函数:

(pprof) list DFS
Total: 2525 samples
ROUTINE ====================== main.DFS in /home/rsc/g/benchgraffiti/havlak/havlak1.go
   119    697 Total samples (flat / cumulative)
     3      3  240: func DFS(currentNode *BasicBlock, nodes []*UnionFindNode, number map[*BasicBlock]int, last []int, current int) int {
     1      1  241:     nodes[current].Init(currentNode, current)
     1     37  242:     number[currentNode] = current
     .      .  243:
     1      1  244:     lastid := current
    89     89  245:     for _, target := range currentNode.OutEdges {
     9    152  246:             if number[target] == unvisited {
     7    354  247:                     lastid = DFS(target, nodes, number, last, lastid+1)
     .      .  248:             }
     .      .  249:     }
     7     59  250:     last[number[currentNode]] = lastid
     1      1  251:     return lastid
(pprof)

该清单显示了 DFS 函数的源代码(实际上,显示了与正则表达式 DFS 匹配的每个函数)。 前三列是运行该行时的样本数,运行该行或该行调用的代码的样本数,以及文件中的行号。 相关命令 disasm 可以显示函数的反汇编而不是源代码清单; 当有足够的样本时,可以帮助您查看哪些指令很昂贵。 weblist 命令混合了两种模式:它显示了一个源列表,单击一行显示反汇编

由于我们已经知道时间花费在哈希运行时函数实现的映射查找,因此我们最关注的是第二列。 在递归调用 DFS (第247行)中花费了很大一部分时间,符合递归遍历的期望。 排除递归,看起来时间是花费在第242,246和250行的 number map的访问。对于特定查找,map 不是最有效的选择。 正如它们在编译器中一样,basic block 结构具有分配给它们的唯一序列号。 我们可以使用 []int 替代 map[*BasicBlock]int,这是一个由block number索引的slice。 当数组或slice可以使用时,没有理由使用map。

number 从map更改为slice需要在程序中编辑七行,并将其运行时间减少近1/2:

$ make havlak2
go build havlak2.go
$ ./xtime ./havlak2
# of loops: 76000 (including 1 artificial root node)
16.55u 0.11s 16.69r 1321008kB ./havlak2
$

见: havlak1 vs havlak2 diff

GC Memory Profile

我们可以再次运行 profiler 来确认 main.DFS 不再是运行时间中重要的组成部分:

$ make havlak2.prof
./havlak2 -cpuprofile=havlak2.prof
# of loops: 76000 (including 1 artificial root node)
$ go tool pprof havlak2 havlak2.prof
Welcome to pprof!  For help, type 'help'.
(pprof)
(pprof) top5
Total: 1652 samples
     197  11.9%  11.9%      382  23.1% scanblock
     189  11.4%  23.4%     1549  93.8% main.FindLoops
     130   7.9%  31.2%      152   9.2% sweepspan
     104   6.3%  37.5%      896  54.2% runtime.mallocgc
      98   5.9%  43.5%      100   6.1% flushptrbuf
(pprof)

条目 main.DFS 不再出现在 profile 中,程序 runtime 的其余部分也已消失。 现在该程序花费大部分时间来分配内存和垃圾收集( runtime.mallocgc,分配和运行定期垃圾收集,占54.2%的时间)。 为了找出垃圾收集器运行如此多的原因,我们必须找出分配内存的原因。 一种方法是向程序添加 memory profiling。 我们改写程序,如果提供 -memprofile flag,程序在循环查找一次迭代后停止,写入 memory profile,并退出:

var memprofile = flag.String("memprofile", "", "write memory profile to this file")
...
    FindHavlakLoops(cfgraph, lsgraph)
    if *memprofile != "" {
        f, err := os.Create(*memprofile)
        if err != nil {
            log.Fatal(err)
        }
        pprof.WriteHeapProfile(f)
        f.Close()
        return
    }

我们使用 -memprofile flag 调用程序写入 profile:

$ make havlak3.mprof
go build havlak3.go
./havlak3 -memprofile=havlak3.mprof
$

见: diff from havlak2

我们以完全相同的方式使用 go tool pprof。 现在我们正在检查的样本是内存分配,而不是时钟周期。

$ go tool pprof havlak3 havlak3.mprof
Adjusting heap profiles for 1-in-524288 sampling rate
Welcome to pprof!  For help, type 'help'.
(pprof) top5
Total: 82.4 MB
    56.3  68.4%  68.4%     56.3  68.4% main.FindLoops
    17.6  21.3%  89.7%     17.6  21.3% main.(*CFG).CreateNode
     8.0   9.7%  99.4%     25.6  31.0% main.NewBasicBlockEdge
     0.5   0.6% 100.0%      0.5   0.6% itab
     0.0   0.0% 100.0%      0.5   0.6% fmt.init
(pprof)

命令 go tool pprof 显示 FindLoops 分配了正在使用的大约56.3个82.4 MB; CreateNode 占另外的17.6 MB。 为了减少开销,内存分析器分配每半兆字节大约只记录一个块的信息。(“1-in-524288采样率”),因此这些是实际计数的近似值。

目录
相关文章
|
存储 缓存 算法
译 | Profiling Go Programs(四)
译 | Profiling Go Programs
75 0
|
Java Go
译 | Profiling Go Programs(三)
译 | Profiling Go Programs(三)
48 0
|
算法 Ubuntu Java
译 | Profiling Go Programs(一)
译 | Profiling Go Programs
106 0
|
4天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
33 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
24天前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
37 7
|
24天前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
24天前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
99 71
|
23天前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
103 67
|
26天前
|
Go 索引
go语言for遍历数组或切片
go语言for遍历数组或切片
94 62
|
24天前
|
存储 Go
go语言中映射
go语言中映射
36 11