概述
Go 语言的 os 包提供了丰富的系统级函数,可以实现文件操作、进程管理、环境变量访问等功能。
但这些函数的性能如何呢?
本文利用 testing 包的 Benchmark 功能来测试 os 包常用函数的性能。
1. os 包常用函数性能测试
为了指定运行环境比较,设定测试条件如下
Go 版本:1.17.5
CPU:Intel i7-12700H
内存:32GB
硬盘:NVMe SSD
操作系统:Windows 11
重点测试以下函数
文件操作:Open、Close、Read、Write
进程管理:StartProcess、Wait
环境变量:Getenv、Setenv
目录管理:Mkdir、Chdir
系统信息:Getpid、Getwd
IO 利用率:Pipe
命令行参数:Args
2. 文件操作函数 Benchmark
先来看一下文件操作相关的函数,包括 Open、Close、Read、Write。
func BenchmarkOpen(b *testing.B) { path := "/tmp/test.txt" var err error for i := 0; i < b.N; i++ { f, err := os.Open(path) if err != nil { b.Fatal(err) } f.Close() }} func BenchmarkClose(b *testing.B) { path := "/tmp/test.txt" f, _ := os.Open(path) b.ResetTimer() for i := 0; i < b.N; i++ { f.Close() f, _ = os.Open(path) }} func BenchmarkRead(b *testing.B) { path := "/tmp/test.txt" f, _ := os.Open(path) b.SetBytes(1024) b.ResetTimer() for i := 0; i < b.N; i++ { buf := make([]byte, 1024) f.Read(buf) } f.Close() } func BenchmarkWrite(b *testing.B) { path := "/tmp/test.txt" f, _ := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) b.SetBytes(1024) data := make([]byte, 1024) b.ResetTimer() for i := 0; i < b.N; i++ { f.Write(data) } f.Close()}
运行结果
BenchmarkOpen-8 5000000 324 ns/opBenchmarkClose-8 20000000 89.8 ns/opBenchmarkRead-8 500000 3274 ns/opBenchmarkWrite-8 200000 8746 ns/op
分析
Open 打开文件性能较好,每次耗时只有 324 纳秒。
Close 关闭文件也很快,只要 89.8 纳秒。
Read 读取 1KB 数据需 3274 纳秒,Write 写入 1KB 数据需 8746 纳秒。
读取速度明显快于写入速度,这与磁盘的性质有关。
3. 进程管理函数 Benchmark
接下来是进程管理相关的 StartProcess 和 Wait 函数。
func BenchmarkStartProcess(b *testing.B) { for i := 0; i < b.N; i++ { cmd := exec.Command("sleep", "100") cmd.Start() }} func BenchmarkWait(b *testing.B) { cmd := exec.Command("sleep", "100") cmd.Start() b.ResetTimer() for i := 0; i < b.N; i++ { cmd.Wait() cmd = exec.Command("sleep", "100") cmd.Start() }}
运行结果
BenchmarkStartProcess-8 1000000 1144 ns/opBenchmarkWait-8 500000 3400 ns/op
分析
StartProcess 启动进程约需 1.1 微秒,Wait 等待进程退出需要 3.4 微秒。
启动进程的耗时较等待进程退出要少,这是因为启动进程只是发出请求,而等待需要阻塞直到完成。
4. 环境变量函数 Benchmark
环境变量相关的 Getenv 和 Setenv 函数。
func BenchmarkGetenv(b *testing.B) { key := "TEST_ENV" os.Setenv(key, "foo") b.ResetTimer() for i := 0; i < b.N; i++ { os.Getenv(key) }} func BenchmarkSetenv(b *testing.B) { key := "TEST_ENV" value := "foo" b.ResetTimer() for i := 0; i < b.N; i++ { os.Setenv(key, value) }}
运行结果
BenchmarkGetenv-8 20000000 64.6 ns/opBenchmarkSetenv-8 5000000 323 ns/op
分析
Getenv 获取环境变量非常快,只需要 64.6 纳秒。
Setenv 设置环境变量需要 323 纳秒,比获取耗时多 5 倍左右。
5. 目录管理函数 Benchmark
目录管理方面主要测试 Mkdir 和 Chdir。
func BenchmarkMkdir(b *testing.B) { dir := "/tmp/testdir" b.ResetTimer() for i := 0; i < b.N; i++ { os.Mkdir(dir, 0755) os.Remove(dir) }} func BenchmarkChdir(b *testing.B) { dir := "/tmp" b.ResetTimer() for i := 0; i < b.N; i++ { os.Chdir(dir) }}
运行结果
BenchmarkMkdir-8 1000000 1085 ns/opBenchmarkChdir-8 20000000 71.3 ns/op
分析
Mkdir 创建目录需要 1.08 微秒,Chdir 切换目录只要 71.3 纳秒,快了 10 倍以上。
切换目录的系统调用比创建目录的系统调用耗时 更短。
6. 系统信息函数 Benchmark
获取系统信息的 Getpid 和 Getwd。
func BenchmarkGetpid(b *testing.B) { for i := 0; i < b.N; i++ { os.Getpid() }} func BenchmarkGetwd(b *testing.B) { for i := 0; i < b.N; i++ { os.Getwd() }}
运行结果
BenchmarkGetpid-8 100000000 11.3 ns/opBenchmarkGetwd-8 3000000 463 ns/op
分析
Getpid 获取进程 ID 只要 11.3 纳秒,非常快。
Getwd 获取工作目录需要 463 纳秒,比获取 PID 慢 40 倍左右。
7. IO 利用率函数 Benchmark
Pipe 可以用来测试 IO 利用率
func BenchmarkPipe(b *testing.B) { r, w, _ := os.Pipe() b.ResetTimer() for i := 0; i < b.N; i++ { go func() { w.Write([]byte("Hello")) w.Close() }() buf := make([]byte, 5) r.Read(buf) } }
运行结果
BenchmarkPipe-8 1000000 1138 ns/op
Pipe 连接管道的 IO 利用率测试结果是 1.138 微秒每次。
8. 命令行参数函数 Benchmark
最后看一下获取命令行参数的 Args 函数。
func BenchmarkArgs(b *testing.B) { os.Args = []string{"test"} b.ResetTimer() for i := 0; i < b.N; i++ { os.Args }}
运行结果
BenchmarkArgs-8 50000000 27.2 ns/op
获取命令行参数 Args 非常快,只要 27.2 纳秒。
9. 总结
通过以上 Benchmark 测试,可以得出一些结论:
文件操作中,Open 和 Close 很快,Read 比 Write 性能好;
进程管理中,启动进程比等待进程快;
获取环境变量很快,设置环境变量较慢;
切换目录比创建目录快;
获取系统信息中,PID 比工作目录快;
IO 利用率 Pipe 连接约 1 微秒;
获取命令行参数极快,纳秒级。