go语言中常用的功能之十二(web基准测试和并发测试)

go语言常见的web基准测试和并发测试

1. 基准测试

基准测试又称为性能测试,用于测试函数的执行效率占用内存等 书写遵循下面的
规范即可

  1. 文件名称以源文件名_test.go
  2. 函数名以 BenchmarkYourFuncName(b testing.B)
  3. 批量基准测试使用如下代码格式:
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完成

未完待续。。。

猜你喜欢

转载自blog.csdn.net/wujiangwei567/article/details/87983193