go语言常见的web基准测试和并发测试
1. 基准测试
基准测试又称为性能测试,用于测试函数的执行效率占用内存等 书写遵循下面的
规范即可
- 文件名称以
源文件名_test.go
- 函数名以
BenchmarkYourFuncName(b testing.B)
- 批量基准测试使用如下代码格式:
for i := 0; i < b.N ; i++ {
//about your func call
}
这里的 b.N
是由系统计算的常量,不必考虑
我们来看个例子
calc.go
package calc
func Dec(a, b int) int {
res := a - b
if res < 0 {
return res
}
return res
}
calc_test.go
package calc
import "testing"
func BenchmarkDec(b *testing.B) {
m,n := 12356596,565858
except := m - n
for i := 0; i < b.N; i++ {
fact := Dec(m, n)
if except != fact {
b.Errorf("%d - %d except %d but fact %d \n", m, n, except, fact)
}
}
}
运行:
go test -bench .
该命令只运行 Benchmark
开头相关函数对应的文件,如果你在项目根目录执行,
可能会报错,最好转到包所在目录再执行 ,参数.
是所有 Benchmark
函数
如果指定函数加上 -test.run 函数名称
打印结果:
go test -test.run BenchmarkDec -cpu 4 -benchmem -bench .
goos: windows
goarch: amd64
pkg: calc
BenchmarkDec-4 2000000000 0.31 ns/op 0 B/op 0 allocs/op
PASS
ok calc 0.909s
这里几个参数说明一下;
-test.run 指定执行的函数名称
-cpu 指定使用cpu核数
-benchmem 是显示操作占用内存
-bench 执行基准测试 (如果没有此参数 将会给b.N
赋值 1 也就类似于普通测试)
参数 | 说明 |
---|---|
BenchmarkDec-4 | 执行函数名称及使用核数 |
2000000000 | 运行次数 |
0.31 ns/op | 每个操作时长 |
0 B/op | 每个操作分配内存 |
0 allocs/op | 动态分配内存次数 |
2. web测试
web测试可以使用
httptest
库来实现
web.go
package web
import (
"net/http"
)
func HandleJson(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status":0,"data":{"username":"friker","avoter":"http://a.test.com/1.jpg"}`))
}
web_test.go
package web
import (
"net/http"
"net/http/httptest"
"testing"
)
func BenchmarkHandleJson(b *testing.B) {
for i := 0; i < b.N; i++ {
// 构造一个请求对象 这里传入的url 不会被真正的请求
req, err := http.NewRequest("GET", "/list", nil)
if err != nil {
b.Fatal(err)
}
responseRecorder := httptest.NewRecorder()
// HandlerFunc 类型是一个适配器允许 普通函数作为 HTTP 处理函数. 如果 f 是一个携带着相同签名的函数
//, HandlerFunc(f) 是一个处理者的身份手动调用 f 类似于指定类型函数HandlerFunc的实例化
handler := http.HandlerFunc(HandleJson)
//w ResponseWriter, r *Request 调用实例化的还书 传入请求和响应记录着
handler.ServeHTTP(responseRecorder, req)
if status := responseRecorder.Code; status != http.StatusOK {
b.Errorf("wrong request code")
}
expected := `{"status":0,"data":{"username":"friker","avoter":"http://a.test.com/1.jpg"}`
if responseRecorder.Body.String() != expected {
b.Errorf("wrong request data")
}
}
}
执行
go test -benchmem -cpu 4 -bench .
打印结果:
goos: windows
goarch: amd64
pkg: web
BenchmarkHandleJson-4 1000000 1022 ns/op 1312 B/op 12 allocs/op
PASS
ok web 1.277s
3. 并发测试
并发相关的性能测试
pip.go
package pip
import (
"sync"
"testing"
)
func BenchmarkGood_Find(b *testing.B) {
var lock sync.RWMutex
good := Good{1500,&lock}
b.SetParallelism(15)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
good.Find()
good.Find()
good.OperStock(-1)
}
})
}
pip_test.go
package pip
import "sync"
type Good struct {
stock int
l *sync.RWMutex
}
func (g *Good)Find() int {
g.l.RLock()
stock := g.stock
g.l.RUnlock()
return stock
}
func (g *Good)OperStock(n int) {
g.l.Lock()
g.stock += n
g.l.Unlock()
}
我们执行:
go test -benchmem -cpu 4 -bench .
打印结果:
goos: windows
goarch: amd64
pkg: pip
BenchmarkGood_Find-4 200000 6955 ns/op 1319 B/op 3 allocs/op
PASS
ok pip 2.418s
然后将 pip.go
中的锁改成互斥锁,再次执行
打印结果:
goos: windows
goarch: amd64
pkg: pip
BenchmarkGood_Find-4 200000 6860 ns/op 1233 B/op 2 allocs/op
PASS
ok pip 2.271s
这个结果很意外,并不是传统的读多的数据使用读写锁性能更好
可以看到基准测试中使用:
b.SetParallelism(gofuncNum)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
your func here ...
}
})
其中 运行时可能分到的最大进程数 * gofuncNum
得到当前并发调用的次数
b.parallelism * runtime.GOMAXPROCS(0)
该数不易过大,否则会导致死机,反正 windows 会卡死
如果是普通测试 在测试函数开始前加入
t.Parallel()
并使用 sync.WaitGroup
系列函数监控是否所有goroutine完成
未完待续。。。