代码分析示例

Go 支持 CPU 分析内存 分析 两种分析方式。不推荐同时对一个应用做俩种类型的分析,因为这俩种不同的分析方式不能相互工作的很好。然而,profileMe.go 应用是一个例外,因为它被用来说明这个两种技术。

用于分析的 Go 代码保存再 profileMe.go,并分为五部分来介绍。profileMe.go 的第一部分 Go 代码如下:

package main
import (
    "fmt"
    "math"
    "os"
    "runtime"
    "runtime/pprof"
    "time"
)
func fibo1(n int) int64 {
    if n == 0 || n == 1 {
        return int64(n)
    }
    time.Sleep(time.Millisecond)
    return int64(fibo2(n-1)) + int64(fibo2(n-2))
}

注意给您的程序创建分析数据必须直接或间接引入 runtime/pprof 包。在 fibo1() 函数调用 time.Sleep() 是为了使它慢一点。在这节尾声您将明白为什么。

profileMe.go 的第二段代码如下:

func fibo2(n int) int {
    fn := make(map[int]int)
    for i:= 0; i <= n; i++ {
        var f int
        if i <= 2 {
            f = 1
        } else {
            f = fn[i-1] + fn[i-2]
        }
        fn[i] = f
    }
    time.Sleep(50 * time.Millisecond)
    return fn[n]
}

上面这段代码包含另个 Go 函数的实现,使用另一个算法计算斐波那契数列。

profileMe.go 的第三部分代码如下:

func N1(n int) bool {
    k := math.Floor(float64(n/2 + 1))
    for i := 2; i < int(k); i++ {
        if (n % i) == 0 {
            return false
        }
    }
    return true
}

func N2(n int) bool {
    for i := 2 ; i < n; i++ {
        if (n % i) == 0 {
            return false
        }
    }
    return true
}

N1()N2() 函数都是用来找出给定整数是否是素数。第一个函数已被优化,因为它的 for 循环迭代大约超过第二个函数的 for 循环使用的数字的一半。因为它们运行的都相当慢,所以没必要在此调用 time.Sleep()

profileMe.go 的第四部分如下:

func main() {
    cpuFile, err := os.Create("/tmp/cpuProfile.out")
    if err != nil {
        fmt.Println(err)
        return
    }
    pprof.StartCPUProfile(cpuFile)
    defer pprof.StopCPUProfile()
    total := 0
    for i := 2; i < 100000; i++ {
        n := N1(i)
        if n {
            total = total + 1
        }
    }
    fmt.Println("Total primes:", total)
    total = 0
    for i := 2; i < 100000; i++ {
        n := N2(i)
        if n {
            total = total + 1
        }
    }
    fmt.Println("Total primes:", total)
    for i := 1; i < 90; i++ {
        n := fibo1(i)
        fmt.Println(n, " ")
    }
    fmt.Println()
    for i := 1; i < 90; i++ {
        n := fibo2(i)
        fmt.Println(n, " ")
    }
    fmt.Println()
    runtime.GC()

调用 os.Create() 创建一个文件用于写分析数据。调用 pprof.StartCPUProfile() 开始程序的 CPU 分析,然后调用 ppprof.StopCPUProfile() 停止。

profileMe.go 的最后部分如下:

    // Memory profiling!
    memory, err := os.Create("/tmp/memoryProfile.out")
    if err != nil {
        fmt.Println(err)
        return
    }
    defer memory.Close()
    for i := 0; i < 10; i++ {
        s := make([]byte, 50000000)
        if s == nil {
            fmt.Println("Operation failed!")
        }
        time.Sleep(50 * time.Millisecond)
    }
    err = pprof.WriteHeapProfile(memory)
    if err != nil {
        fmt.Println(err)
        return
    }
}

最后这部分,您可以看到 内存分析 技术是如何工作的。它与 CPU 分析很相似, 您再次需要一个文件来写分析数据。

执行 profileMe.go 将产生如下输出:

除了输出外,程序也搜集分析数据到俩个文件:

$ cd /tmp
$ ls -l *Profile*
-rw-r--r-- 1 mtsouk wheel 1965 Mar 8 16:53 cpuProfile.out
-rw-r--r-- 1 mtsouk wheel 484 Mar 8 16:53 memoryProfile.out

只有在收集了分析数据后,您才能开始检查它。

因此,您现在可以启动命令行分析器来检查 CPU 数据,如下所示:

$ go tool pprof /tmp/cpuProfile.out
Main binary filename not availabe.
Type: cpu
Time: Mar 8, 2018 at 4:53pm (EET)
Duration: 19.12s, Total samples = 4.18s (21.86%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)

在分析器 shell 中输入 help 将产生如下输出:


top 命令以文本形式返回前10个条目:

输出的第一行,显示函数占程序总执行时间的 98.56%。 main.N2 函数显著的占程序执行时间的 46.65%。

top10 --cum 命令返回每个函数执行的累计时间:

如果您想找出一个特定函数到底发生了什么的话,您可以使用 list 命令,后跟函数名(和包名组合),您将获得该函数执行的更多详细信息:

这个输出显示了 main.N1for 循环几乎占用了整个函数的执行时间。特别是 if (n % i) == 0 表达式占了整个函数执行时间的 910ms 中的 850ms

您也可以使用 pdf 命令从 Go 分析器的shell 创建一个分析数据的 PDF 输出:

(pprof) pdf
Generating report in prfile001.pdf

注意您需要 Graphviz 为了使生成的 PDF 文件能被您喜爱的 PDF 阅读器浏览。

最后,一个警告:如果您的程序执行太快,那么分析器将没有足够的时间获取样本,从而当您加载数据文件时,可能看到 Total samples = 0 的输出。如果那样的话,您将不能从分析过程中获得任何有用的信息。这就是为什么在 profileMe.go 的部分函数中使用 time.Sleep() 函数的原因:

最后编辑: kuteng  文档更新时间: 2021-03-27 20:14   作者:kuteng