CppGuide社区 CppGuide社区
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 最新谷歌C++风格指南(含C++17/20)
  • C++17详解
  • C++20完全指南
  • C++23快速入门
  • C++语言面试问题集锦
  • 🔥C/C++后端开发常见面试题解析 (opens new window)
  • 网络编程面试题 (opens new window)
  • 网络编程面试题 答案详解 (opens new window)
  • 聊聊WebServer作面试项目那些事儿 (opens new window)
  • 字节跳动面试官现身说 (opens new window)
  • 技术简历指南 (opens new window)
  • 🔥交易系统开发岗位求职与面试指南 (opens new window)
  • 第1章 高频C++11重难点知识解析
  • 第2章 Linux GDB高级调试指南
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 高性能网络通信协议设计精要
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 后端服务重要模块设计探索
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 源码分析系列

    • leveldb源码分析
    • libevent源码分析
    • Memcached源码分析
    • TeamTalk源码分析
    • 优质源码分享 (opens new window)
    • 🔥远程控制软件gh0st源码分析
  • 从零手写C++项目系列

    • C++游戏编程入门(零基础学C++)
    • 🔥使用C++17从零开发一个调试器 (opens new window)
    • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
    • 🔥使用C++从零写一个C语言编译器 (opens new window)
    • 从零用C语言写一个Redis
  • Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • Go语言特性

    • Go开发实用指南
    • Go系统接口编程
    • 高效Go并发编程
    • Go性能调优
    • Go项目架构设计
  • Go项目实战

    • 使用Go从零开发一个数据库
    • 🔥使用Go从零开发一个编译器 (opens new window)
    • 🔥使用Go从零开发一个解释器 (opens new window)
    • 🔥用Go从零写一个编排器(类Kubernetes) (opens new window)
  • Rust编程

    • Rust编程指南
  • 数据库

    • SQL零基础指南
    • MySQL开发与调试指南
GitHub (opens new window)
  • Go开发实用指南 说明
  • 第1章 Go项目如何组织
  • 第2章 字符串处理
  • 第3章 处理日期和时间
  • 第4章 使用数组、切片和映射
  • 第5章 使用类型、结构体和接口
  • 第6章 使用泛型
  • 第7章 并发
  • 第8章 错误与恐慌(panic)
  • 第9章 context包
  • 第10章 处理大量数据
  • 第11章 处理JSON数据
  • 第12章 进程
  • 第13章 网络编程
  • 第14章 流式输入/输出
  • 第15章 数据库
  • 第16章 日志记录
  • 第17章 测试、基准测试和性能分析
    • 进行单元测试
    • 编写单元测试
      • 操作方法……
    • 运行单元测试
      • 操作方法……
    • 在测试中记录日志
      • 操作方法……
    • 跳过测试
      • 操作方法……
    • 测试HTTP服务器
      • 操作方法……
      • 测试HTTP处理器
      • 操作方法……
      • 检查测试覆盖率
      • 操作方法……
      • 基准测试
      • 编写基准测试
      • 操作方法……
      • 编写具有不同输入大小的多个基准测试
      • 操作方法……
      • 运行基准测试
      • 操作方法……
      • 性能分析
      • 操作方法……
      • 另请参阅
目录

第17章 测试、基准测试和性能分析

# 第17章 测试、基准测试和性能分析

为代码编写测试和基准测试(benchmark)在多个方面对你有所帮助。在开发过程中,测试可以确保你正在开发的功能正常运行,并且在开发工作中不会破坏现有功能。基准测试可以确保你的程序在一定的资源和时间限制内运行。开发完成后,同样的测试和基准测试可以确保任何维护工作(如修复漏洞、增强功能等)不会在现有功能中引入新的漏洞。所以,你应该把编写测试和基准测试视为核心开发活动,在开发程序的同时开发其测试用例。

测试应该专注于测试一切正常时的预期行为(“正向路径测试”)以及出现问题时的情况,而不是覆盖所有的实现路径。为涵盖所有可能的实现选择而编写的测试很快就会变得比程序本身更难维护。你应该在实用性和测试覆盖率之间保持平衡。

本节展示了处理几种常见测试和基准测试场景的惯用方法。本章涵盖以下主题:

  • 进行单元测试
  • 编写单元测试
  • 运行单元测试
  • 在测试中记录日志
  • 跳过测试
  • 测试HTTP服务器
  • 测试HTTP处理程序
  • 检查测试覆盖率
  • 基准测试
  • 编写基准测试
  • 编写不同输入大小的多个基准测试
  • 运行基准测试
  • 性能分析

# 进行单元测试

我们将以一个对 time.Time 值进行升序或降序排序的示例函数为例,代码如下:

package sort
import (
    "sort"
    "time"
)

// Sort times in ascending or descending order
func SortTimes(input []time.Time, asc bool) []time.Time {
    output := make([]time.Time, len(input))
    copy(output, input)
    if asc {
        sort.Slice(output, func(i, j int) bool {
            return output[i].Before(output[j])
        })
        return output
    }
    
    sort.Slice(output, func(i, j int) bool {
        return output[j].Before(output[i])
    })
    
    return output
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

我们将使用Go构建系统和标准库提供的内置测试工具。为此,假设我们将前面的函数存储在一个名为 sort.go 的文件中。那么,这个函数的单元测试将放在与 sort.go 同一目录下的 sort_test.go 文件中。Go构建系统会将以 _test.go 结尾的源文件识别为单元测试文件,并在常规构建时将其排除在外。

# 编写单元测试

理想情况下,单元测试用于测试单个单元(一个函数、一组相关函数或某个类型的方法)的行为是否符合预期。

# 操作方法……

  1. 创建以 _test.go 为后缀的单元测试文件。对于 sort.go,我们创建 sort_test.go。以 _test.go 结尾的文件将被排除在常规构建之外:
package sort
1
提示
你也可以在一个以 _test 结尾的单独测试包中编写测试。在这个例子中,包名变为 package sort_test。在单独的包中编写测试,能让你从外部视角测试一个包的函数,因为你无法访问被测试包中未导出的名称。你还需要导入被测试的包。
  1. Go测试系统将运行符合 Test<Feature>(*testing.T) 模式的函数。声明一个符合此模式的测试函数,并编写一个测试行为的单元测试:
func TestSortTimesAscending(t *testing.T) {
    // 2.a Prepare input data
    input := []time.Time{
        time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
        time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
        time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
        time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
        time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
    }
    
    // 2.b Call the function under test
    output := SortTimes(input, true)
    
    // 2.c Make sure the output is what is expected
    for i := 1; i < len(output); i++ {
        if!output[i - 1].Before(output[i]) {
            t.Error("Wrong order")
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  1. 测试函数的结构通常遵循以下模式:
    • 准备输入数据以及被测试函数运行所需的任何环境。
    • 使用必要的输入调用被测试函数。
    • 确保被测试函数返回了正确的结果或行为符合预期。
  2. 如果测试检测到错误,使用 t.Error 系列函数通知测试系统测试失败。

# 运行单元测试

使用Go构建系统工具来运行单元测试。

# 操作方法……

  1. 要运行当前包中的所有单元测试,输入以下命令:
go test
PASS
ok  github.com/bserdar/go-recipes-book/chp15/sort/sort    0.001s
1
2
3
  1. 要运行某个包中的所有单元测试,输入以下命令:
go test <packageName>
1

或者,输入以下命令:

go test ./<folder>
1

以下是一个示例:

go test github.com/bserdar/go-recipes-book/chp15/sort/sort
1

或者,你可以输入以下命令:

go test ./sort
1
  1. 要递归地运行模块中所有包的所有单元测试,输入以下命令:
go test ./...
1

在模块的根目录下执行此命令。 4. 要运行当前包中的单个测试,输入以下命令:

go test -run TestSortTimesAscending
1

这种形式将 -run 标志后的测试名称视为正则表达式,并运行所有包含该字符串的测试。例如,go test -run Sort 将运行所有名称中包含 Sort 的测试。如果你只想运行特定的测试,可以相应地构造正则表达式:

go test -run ^TestSortTimesAscending$
1

这里,^ 表示正则表达式中字符串的开头,$ 表示字符串的结尾。

例如,以下命令将运行所有以 Ascending 结尾的测试:

go test -run Ascending$
1

# 在测试中记录日志

通常,额外的日志记录功能对测试很有用,特别是在测试失败时,可以显示关键变量的状态。默认情况下,如果测试通过,Go测试执行器不会打印任何日志信息,但如果测试失败,日志信息也会包含在输出中。

# 操作方法……

  1. 使用 testing.T.Log 和 testing.T.Logf 函数在测试中记录日志消息:
func TestSortTimeAscending(t *testing.T) {
    ...
    t.Logf("Input: %v",input)
    output := SortTimes(input,true)
    t.Logf("Output: %v", output)
}
1
2
3
4
5
6
  1. 运行测试。如果测试通过,不会打印日志信息。如果测试失败,则会打印日志。

要在运行测试时显示日志,可以使用 -v 标志:

$ go test -v
=== RUN   TestSortTimesAscending
sort_test.go:17: Input: [2023-02-01 12:08:37 -0700 MST 2021- 05-06 09:48:11 -0600 MDT 2022-11-13 17:13:54 -0700 MST 2022-06-
23 22:29:28 -0600 MDT 2023-03-17 04:05:09 -0600 MDT]
sort_test.go:19: Output: [2021-05-06 09:48:11 -0600 MDT 2022-06-23 22:29:28 -0600 MDT 2022-11-13 17:13:54 -0700 MST
2023-02-01 12:08:37 -0700 MST 2023-03-17 04:05:09 -0600 MDT]
--- PASS: TestSortTimesAscending (0.00s)
1
2
3
4
5
6
7

# 跳过测试

你可以根据输入标志跳过某些测试。这个功能让你可以进行快速测试(只运行部分测试)和全面测试(运行所有测试)。

# 操作方法……

  1. 对于那些应该在简短测试运行中被排除的测试,检查 testing.Short() 标志:
func TestService(t *testing.T) {
    if testing.Short() {
        t.Skip("Service")
    }
   ...
}
1
2
3
4
5
6
  1. 使用 test.short 标志运行测试:
$ go test -test.short -v
=== RUN   TestService
service_test.go:15: Service --- SKIP: TestService (0.00s)
=== RUN   TestHandler
--- PASS: TestHandler (0.00s)
PASS
1
2
3
4
5
6

# 测试HTTP服务器

net/http/httptest 包是对 testing 包的补充,它提供了HTTP服务器测试工具,让你能够快速创建测试用的HTTP服务器。

在本节中,假设我们将排序函数扩展为一个HTTP服务,代码如下:

package service
import (
    "encoding/json"
    "io"
    "net/http"
    "time"
    "github.com/bserdar/go-recipes-book/chp15/sort/sort"
)

// Common handler function for parsing the input, sorting, and
// preparing the output
func HandleSort(w http.ResponseWriter, req *http.Request, ascending
bool) {
    var input []time.Time
    data, err := io.ReadAll(req.Body)

    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    if err := json.Unmarshal(data, &input); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    output := sort.SortTimes(input, ascending)
    data, err = json.Marshal(output)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.Write(data)
}

// Prepares a multiplexer that handles POST /sort/asc and POST /sort/
// desc endpoints
func GetServeMux() *http.ServeMux {
    mux := http.NewServeMux()
    mux.HandleFunc("POST /sort/asc", func(w http.ResponseWriter, req
    *http.Request) {
        HandleSort(w, req, true)
    })
    mux.HandleFunc("POST /sort/desc", func(w http.ResponseWriter, req
    *http.Request) {
        HandleSort(w, req, false)
    })
    return mux
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

GetServeMux 函数准备了一个请求多路复用器,分别处理 POST /sort/asc 和 POST /sort/desc 这两个HTTP端点,用于处理升序和降序排序请求。输入是一个时间值的JSON数组,处理程序返回一个已排序的JSON数组。

# 操作方法……

  1. 使用net/http/httptest包,该包支持测试服务器:
import (
    "net/http/httptest"
    "testing"
   ...
)
1
2
3
4
5
  1. 在测试函数中,创建一个处理器或多路复用器,并使用它创建一个测试服务器。确保测试结束时服务器关闭——使用defer server.Close():
func TestService(t *testing.T) {
    mux := GetServeMux()
    server := httptest.NewServer(mux)
    defer server.Close()
1
2
3
4
  1. 使用server.URL调用服务器。httptest.NewServer函数会将其初始化为使用一个未被占用的本地端口。在以下示例中,我们向服务器发送无效输入,以验证服务器是否返回错误:
rsp, err := http.Post(server.URL+"/sort/asc", "application/json", strings.NewReader("test"))
if err != nil {
    t.Error(err)
    return
}

// 必须返回HTTP错误
if rsp.StatusCode/100 == 2 {
    t.Errorf("Error was expected")
    return
}
1
2
3
4
5
6
7
8
9
10
11

注意,http.Post函数不会返回错误。http.Post返回错误意味着POST操作失败。在这种情况下,POST操作成功,但返回了一个HTTP错误状态码。 4. 你可以多次调用服务器,以测试不同的输入并检查输出:

data, err := json.Marshal([]time.Time{
    time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
    time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
    time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
    time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
    time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
})
if err != nil {
    t.Error(err)
    return
}

rsp, err = http.Post(server.URL+"/sort/asc", "application/json", bytes.NewReader(data))
if err != nil {
    t.Error(err)
    return
}

defer rsp.Body.Close()
if rsp.StatusCode != 200 {
    t.Errorf("Expected status code 200, got %d", rsp.StatusCode)
    return
}

var output []time.Time
if err := json.NewDecoder(rsp.Body).Decode(&output); err != nil {
    t.Error(err)
    return
}

for i := 1; i < len(output); i++ {
    if!output[i - 1].Before(output[i]) {
        t.Errorf("Wrong order")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 测试HTTP处理器

net/http/httptest包还包含ResponseRecorder,它可以作为HTTP处理器的http.ResponseWriter,用于在不创建服务器的情况下测试单个处理器。

# 操作方法……

  1. 创建ResponseRecorder:
func TestHandler(t *testing.T) {
    w := httptest.NewRecorder()
1
2
  1. 调用处理器,传递响应记录器而不是http.ResponseWriter:
data, err := json.Marshal([]time.Time{
    time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
    time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
    time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
    time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
    time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
})

if err != nil {
    t.Error(err)
    return
}

req, _ := http.NewRequest("POST", "localhost/sort/asc", bytes.NewReader(data))
req.Header.Set("Content-Type", "application/json")
HandleSort(w, req, true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  1. 响应记录器存储处理器构建的HTTP响应。验证响应是否正确:
if w.Result().StatusCode != 200 {
    t.Errorf("Expecting HTTP 200, got %d", w.Result().StatusCode)
    return
}

var output []time.Time
if err := json.NewDecoder(w.Result().Body).Decode(&output); err != nil {
    t.Error(err)
    return
}

for i := 1; i < len(output); i++ {
    if!output[i - 1].Before(output[i]) {
        t.Errorf("Wrong order")
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 检查测试覆盖率

测试覆盖率报告展示了哪些源代码行被测试覆盖到了。

# 操作方法……

  1. 要快速获得覆盖率结果,可以使用cover标志运行测试:
$ go test -cover
PASS
coverage: 76.2% of statements
1
2
3
  1. 要将测试覆盖率配置文件写入一个单独的文件,以便获取详细报告,可以在运行测试时指定一个覆盖率配置文件名:
$ go test -coverprofile=cover.out
PASS
coverage: 76.2% of statements
1
2
3

然后,你可以使用以下命令在浏览器中查看覆盖率报告:

$ go tool cover -html=cover.out
1

这个命令会打开浏览器,让你查看哪些代码行被测试覆盖到了。

# 基准测试

单元测试检查代码的正确性,而基准测试检查性能和内存使用情况。

# 编写基准测试

与单元测试类似,基准测试存储在_test.go文件中,但这些函数以Benchmark开头,而不是Test。基准测试会给定一个数字N,在运行时测量性能的过程中,你需要重复相同的操作N次。

# 操作方法……

  1. 在其中一个_test.go文件中创建一个基准测试函数。以下示例在sort_test.go文件中:
func BenchmarkSortAscending(b *testing.B) {
1
  1. 在基准测试循环之前进行设置,否则,你测量的将是设置代码的性能,而不是实际算法的性能:
input := []time.Time{
    time.Date(2023, 2, 1, 12, 8, 37, 0, time.Local),
    time.Date(2021, 5, 6, 9, 48, 11, 0, time.Local),
    time.Date(2022, 11, 13, 17, 13, 54, 0, time.Local),
    time.Date(2022, 6, 23, 22, 29, 28, 0, time.Local),
    time.Date(2023, 3, 17, 4, 5, 9, 0, time.Local),
}
1
2
3
4
5
6
7
  1. 编写一个循环,迭代b.N次,并执行要进行基准测试的操作:
for i := 0; i < b.N; i++ {
    SortTimes(input, true)
}
1
2
3
提示
避免在基准测试循环中记录或打印数据。

# 编写具有不同输入大小的多个基准测试

通常你会希望了解算法在不同输入大小下的表现。Go测试框架只提供了基准测试应运行的次数,并没有提供输入大小。可以使用以下模式来测试不同的输入大小。

# 操作方法……

  1. 定义一个未导出的参数化基准测试函数,该函数接受输入大小信息或不同大小的输入。以下示例将元素数量和排序方向作为参数,并在执行基准测试之前创建一个具有给定大小的随机打乱的输入切片:
func benchmarkSort(b *testing.B, nItems int, asc bool) {
    input := make([]time.Time, nItems)
    t := time.Now().UnixNano()
    for i := 0; i < nItems; i++ {
        input[i] = time.Unix(0, t - int64(i))
    }
    rand.Shuffle(len(input), func(i, j int) { input[i], input[j] = input[j], input[i] })
    for i := 0; i < b.N; i++ {
        SortTimes(input, asc)
    }
}
1
2
3
4
5
6
7
8
9
10
11
  1. 通过使用不同的值调用通用基准测试函数来定义导出的基准测试函数:
func BenchmarkSort1000Ascending(b *testing.B) {
    benchmarkSort(b, 1000, true)
}

func BenchmarkSort100Ascending(b *testing.B) {
    benchmarkSort(b, 100, true)
}

func BenchmarkSort10Ascending(b *testing.B) {
    benchmarkSort(b, 10, true)
}

func BenchmarkSort1000Descending(b *testing.B) {
    benchmarkSort(b, 1000, false)
}

func BenchmarkSort100Descending(b *testing.B) {
    benchmarkSort(b, 100, false)
}

func BenchmarkSort10Descending(b *testing.B) {
    benchmarkSort(b, 10, false)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 运行基准测试

Go工具会在运行基准测试之前先运行单元测试——对失败的代码进行基准测试没有意义。

# 操作方法……

  1. 使用go test -bench=<regexp>工具。要运行所有基准测试,可以使用以下命令:
go test -bench=.
1
  1. 如果你只想运行部分基准测试,可以输入基准测试的正则表达式。以下命令仅运行名称中包含1000的基准测试:
go test -bench=1000
goos: linux
goarch: amd64
pkg: github.com/bserdar/go-recipes-book/chp15/sort/sort
cpu: AMD Ryzen 5 7530U with Radeon Graphics
BenchmarkSort1000Ascending-12             9753        105997 ns/op
BenchmarkSort1000Descending-12             9813        105192 ns/op
PASS
1
2
3
4
5
6
7
8

# 性能分析

性能分析器(Profiler)对正在运行的程序进行采样,以确定在某些函数上花费了多少时间。你可以对基准测试进行性能分析,创建一个分析文件,然后检查该文件,以找出程序中的瓶颈。

# 操作方法……

要获取CPU性能分析文件并进行分析,请按照以下步骤操作:

  1. 使用cpuprofile标志运行基准测试:
$ go test -bench=1000Ascending --cpuprofile=profile
goos: linux
goarch: amd64
pkg: github.com/bserdar/go-recipes-book/chp15/sort/sort
cpu: AMD Ryzen 5 7530U with Radeon Graphics
BenchmarkSort1000Ascending-12           10000        106509 ns/op
1
2
3
4
5
6
  1. 使用分析文件启动pprof工具:
$ go tool pprof profile
File: sort.test
Type: cpu
1
2
3
  1. 使用topN命令查看分析文件中排名前N的采样:
(pprof) top5
Showing nodes accounting for 780ms, 71.56% of 1090ms total
Showing top 5 nodes out of 47
flat  flat%   sum%        cum   cum%
250ms 22.94% 22.94%      360ms 33.03%  github.com/bserdar/go-recipes-book/chp15/sort/sort.SortTimes.func1
230ms 21.10% 44.04%      620ms 56.88%  sort.partition_func
120ms 11.01% 55.05%      120ms 11.01%  runtime.memmove
90ms  8.26% 63.30%      340ms 31.19%  internal/reflectlite.Swapper.func9
90ms  8.26% 71.56%      230ms 21.10%  internal/reflectlite.typedmemmove
1
2
3
4
5
6
7
8
9

这表明大部分时间花在了比较两个时间值的匿名函数上。flat列显示了在一个函数中花费的时间,不包括在该函数调用的其他函数中花费的时间。cum代表累积时间,包括在一个函数中花费的时间,定义为函数返回的时间点减去函数开始运行的时间点。也就是说,累积值包括在该函数调用的其他函数中花费的时间。例如,sort.partition_func运行了620毫秒,但其中只有230毫秒花在了sort.partition_func本身,其余时间花在了sort.partition_func调用的其他函数上。 4. 使用web命令查看调用图的可视化表示,以及每个函数花费的时间。

要获取内存性能分析文件并进行分析,请按照以下步骤操作:

  1. 使用memprofile标志运行基准测试:
$ go test -bench=1000Ascending --memprofile=mem
goos: linux
goarch: amd64
pkg: github.com/bserdar/go-recipes-book/chp15/sort/sort
cpu: AMD Ryzen 5 7530U with Radeon Graphics
BenchmarkSort1000Ascending-12           10000        106509 ns/op
1
2
3
4
5
6
  1. 使用分析文件启动pprof工具:
$ go tool pprof mem
File: sort.test
Type: alloc_space
1
2
3
  1. 使用topN命令查看分析文件中排名前N的采样:
(pprof) top5
Showing nodes accounting for 493.37MB, 99.90% of 493.87MB total
Dropped 2 nodes (cum <= 2.47MB)
flat  flat%   sum%        cum   cum%
492.86MB 99.80% 99.80%   493.36MB 99.90%  github.com/bserdar/go-recipes-book/chp15/sort/sort.SortTimes
0.51MB   0.1% 99.90%   493.87MB   100%  github.com/bserdar/go-recipes-book/chp15/sort/sort.benchmarkSort
0     0% 99.90%   493.87MB   100%  github.com/bserdar/go-recipes-book/chp15/sort/sort.BenchmarkSort1000Ascending
0     0% 99.90%   493.87MB   100%  testing. (*B).launch
0     0% 99.90%   493.87MB   100%  testing. (*B).runN
1
2
3
4
5
6
7
8
9

与CPU性能分析文件的输出类似,这个表格显示了每个函数分配的内存量。同样,flat仅指在该函数中分配的内存,cum指在该函数及其调用的任何函数中分配的内存。在这里,你可以看到sort.SortTimes是分配大部分内存的函数。这是因为它首先创建了切片的副本,然后对其进行排序。 4. 使用web命令查看内存分配的可视化表示。

# 另请参阅

  • 关于Go程序性能分析的权威指南可在https://go.dev/blog/pprof (opens new window)上获取。
  • pprof的README文件解释了节点和边的表示形式:https://github.com/google/pprof/blob/main/doc/README.md (opens new window) 。
第16章 日志记录

← 第16章 日志记录

最近更新
01
第二章 关键字static及其不同用法
03-27
02
第一章 auto与类型推导
03-27
03
第四章 Lambda函数
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式