golang筆記09--go語言測驗與性能調優
- 1 介紹
- 2 測驗與性能調優
- 2.1 測驗介紹
- 2.2 代碼覆寫率和性能測驗
- 2.3 使用pprof進行性能調優
- 2.4 測驗http服務器(上)
- 2.5 測驗http服務器(下)
- 2.6 生成檔案和示例代碼
- 3 注意事項
- 4 說明
1 介紹
本文繼上文 golang筆記08–go語言錯誤處理和資源管理, 進一步了解 go語言的錯誤處理和資源管理,以及相應注意事項,
具體包括 : 測驗介紹 、代碼覆寫率和性能測驗、使用pprof進行性能調優、p測驗http服務器(上)、測驗http服務器(下)、生成檔案和示例代碼 等內容,
2 測驗與性能調優
2.1 測驗介紹
傳統測驗 vs 表格測驗:
| 傳統測驗 | 表格測驗 |
|---|---|
| 測驗資料和測驗邏輯混在一起 | 分離的測驗資料和測驗邏輯 |
| 出錯資訊不明確 | 明確的出錯資訊 |
| 一旦一個資料出錯測驗全部結束 | 可以部分失敗 |
兩者測驗代碼結構如下圖所示(左邊傳統、右邊表格):

vim triangle.go
package main
import (
"fmt"
"math"
)
func triangle() {
var a, b int = 3, 4
c := calTriangle(a, b)
fmt.Println(c)
}
func calTriangle(a, b int) int {
var c int
c = int(math.Sqrt(float64(a*a + b*b)))
return c
}
vim triangle_test.go
package main
import "testing"
func TestTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 5},
{5, 12, 13},
{8, 15, 17},
{12, 35, 37},
{3000, 4000, 5000},
//{3000, 4000, 5001},
}
for _, tt := range tests {
if actual := calTriangle(tt.a, tt.b); actual != tt.c {
t.Errorf("calTriangle(%d, %d); got %d; expected %d", tt.a, tt.b, tt.c, calTriangle(tt.a, tt.b))
}
}
}
輸出:
=== RUN TestTriangle
--- PASS: TestTriangle (0.00s)
PASS
出錯輸出:
=== RUN TestTriangle
triangle_test.go:16: calTriangle(3000, 4000); got 5001; expected 5000
--- FAIL: TestTriangle (0.00s)
FAIL
2.2 代碼覆寫率和性能測驗
IDEA 中,對于testing型別的程式,可以直接通過查看 Run TestTriangle in lear… with Coverage來查看測驗代碼的覆寫率(也可以根據需要選擇CPU 或者 Memory Profiler),如下圖所示:


vim triangle.go
package main
import (
"fmt"
"math"
)
func triangle() {
var a, b int = 3, 4
c := calTriangle(a, b)
fmt.Println(c)
}
func calTriangle(a, b int) int {
var c int
c = int(math.Sqrt(float64(a*a + b*b)))
return c
}
vim triangle_test.go
package main
import "testing"
func TestTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 5},
{5, 12, 13},
{8, 15, 17},
{12, 35, 37},
{3000, 4000, 5000},
//{3000, 4000, 5001},
}
for _, tt := range tests {
if actual := calTriangle(tt.a, tt.b); actual != tt.c {
t.Errorf("calTriangle(%d, %d); got %d; expected %d", tt.a, tt.b, tt.c, calTriangle(tt.a, tt.b))
}
}
}
func BenchmarkTriangle(b *testing.B) {
a1, b1, c1 := 30000, 40000, 50000
for i := 0; i < b.N; i++ {
actual := calTriangle(a1, b1)
if actual != c1 {
b.Errorf("calTriangle(%d, %d); got %d; expected %d", a1, b1, c1, calTriangle(a1, b1))
}
}
}
TestTriangle 輸出:
=== RUN TestTriangle
--- PASS: TestTriangle (0.00s)
PASS
BenchmarkTriangle 輸出:
goos: linux
goarch: amd64
pkg: learngo/chapter9/9.1
BenchmarkTriangle
BenchmarkTriangle-4 1000000000 0.285 ns/op 【執行了1000000000此操作,平均每次操作0.285 ns】
PASS
除了上述 ide直接執行測驗用例外,也可以通過命令列執行測驗用例:
1) 在測驗用例當前目錄執行go test就可以執行測驗用例
chapter9/9.1$ go test
2)通過 coverprofile=c.out 可以輸出測驗覆寫率到 c.out 檔案
chapter9/9.1$ go test -coverprofile=c.out
PASS
coverage: 50.0% of statements
ok learngo/chapter9/9.1 0.002s
可以進一步通過 chapter9/9.1$ go tool cover -html c.out 查看覆寫率資訊
3)命令列執行benchmark
go test -bench .
2.3 使用pprof進行性能調優
本案例使用benchmark 的 cpuprofile,結合尋找最長不重復子串來逐步優化程式性能,案例中的 web 圖示需要依賴 graphviz ,
具體思路: -cpuprofile獲取性能資料 --》go tool pprof查看性能資料 --》根據web圖分析慢在哪里 --》優化代碼
vim 9.2.go
package main
import "fmt"
func lengthOfNonRepeatSubStrOld(s string) int {
lastOccurred := make(map[rune]int)
start := 0
maxLength := 0
for i, ch := range []rune(s) {
if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
start = lastI + 1
}
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
var lastOccurred = make([]int, 0xffff) //假定中文字的最大值為65535=0xffff
func lengthOfNonRepeatSubStr(s string) int {
// lastOccurred := make([]int, 0xffff) //假定中文字的最大值為65535=0xffff
for i := range lastOccurred {
lastOccurred[i] = -1
}
start := 0
maxLength := 0
for i, ch := range []rune(s) {
if lastI := lastOccurred[ch]; lastI != -1 && lastI >= start {
start = lastI + 1
}
if i-start+1 > maxLength {
maxLength = i - start + 1
}
lastOccurred[ch] = i
}
return maxLength
}
func main() {
fmt.Println("this chapter 9.3")
str := "黑化肥揮發發灰會花飛灰化肥揮發發黑會飛花"
fmt.Printf("%s lengthOfNonRepeatSubStr = %d ", str, lengthOfNonRepeatSubStr(str))
}
vim nonrepeating_test.go
package main
import "testing"
func BenchmarkSubstr(b *testing.B) {
s := "黑化肥揮發發灰會花飛灰化肥揮發發黑會飛花"
for i := 0; i < 13; i++ {
s = s + s
}
b.Logf("len(s) = %d", len(s))
ans := 8
b.ResetTimer()
for i := 0; i < b.N; i++ {
actual := lengthOfNonRepeatSubStr(s)
if actual != ans {
b.Errorf("got %d for input %s; "+"expected %d", actual, s, ans)
}
}
}
輸出(沒有優化):
$ go test -bench . -cpuprofile cpu.out
goos: linux
goarch: amd64
pkg: learngo/chapter9/9.2
BenchmarkSubstr-4 178 6580817 ns/op
--- BENCH: BenchmarkSubstr-4
nonrepeating_test.go:10: len(s) = 491520
nonrepeating_test.go:10: len(s) = 491520
nonrepeating_test.go:10: len(s) = 491520
PASS
ok learngo/chapter9/9.2 2.016s
輸出(優化rune字串功能):
goos: linux
goarch: amd64
pkg: learngo/chapter9/9.2
BenchmarkSubstr-4 426 2756128 ns/op
--- BENCH: BenchmarkSubstr-4
nonrepeating_test.go:10: len(s) = 491520
nonrepeating_test.go:10: len(s) = 491520
nonrepeating_test.go:10: len(s) = 491520
PASS
ok learngo/chapter9/9.2 1.612s
輸出(將make 放在最外層):
goos: linux
goarch: amd64
pkg: learngo/chapter9/9.2
BenchmarkSubstr-4 486 2332484 ns/op
--- BENCH: BenchmarkSubstr-4
nonrepeating_test.go:10: len(s) = 491520
nonrepeating_test.go:10: len(s) = 491520
nonrepeating_test.go:10: len(s) = 491520
PASS
ok learngo/chapter9/9.2 1.511s
也可以通過命令列來測驗cpu性能:
go test -bench . -cpuprofile cpu.out
可以通過tool進一步查看cpu.out 資訊, go tool pprof cpu.out ->互動終端輸出 web就會顯示出測驗時間關系圖,結果如下(需要安裝graphviz):

從圖中可以看到 2 個map 和 一個rune 發的時間較多,rune 暫時不適合優化,但是 map 可以適當優化為對應的陣列

優化后的耗時圖如下,課件優化后主要時間發在 stringtoslicerune 上了,當然還有少量時間發在 makeslice 上面(0.05s):

進一步將make 移到函式外面,可以發現壓測時間進一步略微減少了,此時結果中以及沒有 makeslice 了:

2.4 測驗http服務器(上)
本案例基于 golang筆記08–go語言錯誤處理和資源管理 中的 filelisting 來測驗 http 服務,具體示例如下:
chapter8/8.3$ tree -L 2
.
├── filelisting
│ └── handler.go
├── web.go
└── web_test.go
vim web_test.go
package main
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func errPanic(_ http.ResponseWriter,
_ *http.Request) error {
panic(123)
}
var tests = []struct {
h appHandler
code int
message string
}{
{errPanic, 500, "Internal Server Error"},
}
func TestErrWarpper(t *testing.T) {
for _, tt := range tests {
f := errWarpper(tt.h)
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://www.imooc.com", nil)
f(response, request)
b, _ := ioutil.ReadAll(response.Body)
body := strings.Trim(string(b), "\n") // web默認回傳有個換行符,此處需要去掉才能正確匹配
if response.Code != tt.code || body != tt.message {
t.Errorf("expect (%d, %s); got (%d, %s)", tt.code, tt.message, response.Code, body)
}
}
}
輸出:
=== RUN TestErrWarpper
--- PASS: TestErrWarpper (0.00s)
PASS
2.5 測驗http服務器(下)
http 測驗通常包括兩種方式:
1) 通過使用假的Request/Response
2)通過起服務器
本案例對上述 2.4 中的測驗 case 進一步豐富, 使之能夠測驗更多例外情況,具體內容如下:
vim web2_test.go
package main
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
func errPanic2(_ http.ResponseWriter,
_ *http.Request) error {
panic(123)
}
type testingUserError string
func (e testingUserError) Error() string {
return e.Message()
}
func (e testingUserError) Message() string {
return string(e)
}
func errUserError(_ http.ResponseWriter,
_ *http.Request) error {
return testingUserError("user error")
}
func errNotFound(_ http.ResponseWriter,
_ *http.Request) error {
return os.ErrNotExist
}
func errNoPermission(_ http.ResponseWriter,
_ *http.Request) error {
return os.ErrPermission
}
func errUnknown(_ http.ResponseWriter,
_ *http.Request) error {
return errors.New("unknown error")
}
func noError(writer http.ResponseWriter,
_ *http.Request) error {
fmt.Fprintln(writer, "no error")
return nil
}
var tests = []struct {
h appHandler
code int
message string
}{
{errPanic2, 500, "Internal Server Error"},
{errUserError, 400, "user error"},
{errNotFound, 404, "Not Found"},
{errNoPermission, 403, "Forbidden"},
{errUnknown, 500, "Internal Server Error"},
{noError, 200, "no error"},
}
func TestErrWarpper2(t *testing.T) {
for _, tt := range tests {
f := errWarpper(tt.h)
response := httptest.NewRecorder()
request := httptest.NewRequest(http.MethodGet, "http://www.imooc.com", nil)
f(response, request)
b, _ := ioutil.ReadAll(response.Body)
body := strings.Trim(string(b), "\n") // web默認回傳有個換行符,此處需要去掉才能正確匹配
if response.Code != tt.code || body != tt.message {
t.Errorf("expect (%d, %s); got (%d, %s)", tt.code, tt.message, response.Code, body)
}
}
}
func TestErrWarpperInserver(t *testing.T) {
for _, tt := range tests {
f := errWarpper(tt.h)
server := httptest.NewServer(http.HandlerFunc(f))
resp, _ := http.Get(server.URL)
b, _ := ioutil.ReadAll(resp.Body)
body := strings.Trim(string(b), "\n") // web默認回傳有個換行符,此處需要去掉才能正確匹配
if resp.StatusCode != tt.code || body != tt.message {
t.Errorf("expect (%d, %s); got (%d, %s)", tt.code, tt.message, resp.StatusCode, body)
}
}
}
輸出(TestErrWarpper2):
=== RUN TestErrWarpper2
gopm WARN error occurred handling request: user error
gopm WARN error occurred handling request: file does not exist
gopm WARN error occurred handling request: permission denied
gopm WARN error occurred handling request: unknown error
--- PASS: TestErrWarpper2 (0.00s)
PASS
輸出(TestErrWarpperInserver):
=== RUN TestErrWarpperInserver
gopm WARN error occurred handling request: user error
gopm WARN error occurred handling request: file does not exist
gopm WARN error occurred handling request: permission denied
gopm WARN error occurred handling request: unknown error
--- PASS: TestErrWarpperInserver (0.00s)
PASS
2.6 生成檔案和示例代碼
go語言中可以通過go doc 查看代碼的包和對應的函式,也可以查看系統檔案功能,
1) 通過 go doc 查看queue 的檔案資訊
chapter6/queue$ go doc
package queue // import "learngo/chapter6/queue"
type Queue []interface{}
2)通過 go doc 查看具體資料結構資訊
chapter6/queue$ go doc Queue
go doc Queue
package queue // import "."
type Queue []interface{}
func (q *Queue) IsEmpty() bool
func (q *Queue) Pop() interface{}
func (q *Queue) Push(v interface{})
3) 查看go庫函式說明檔案
$ go doc fmt.println
4)輸出godoc 的 http 服務器
$ godoc -http :6060
若在專案目錄下起,則會包括專案的檔案資訊
5) 若需要添加檔案,則直接將注釋卸載函式上一或行即可,例如
// this is push interface
func (q *Queue) Push(v interface{}) {
*q = append(*q, v)
}
6) go 中也可以新建Example示例代碼,并能檢查相關的結果,如果生成對應的godoc,則會生成對應的 Example的 Code 和 Output,具體代碼如下
func ExampleQueue_Pop() {
q := Queue{1}
q.Push(2)
q.Push(3)
fmt.Println(q.Pop())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
fmt.Println(q.Pop())
fmt.Println(q.IsEmpty())
// Output:
// 1
// 2
// false
// 3
// true
}
通過本地 godoc 起一個檔案查詢服務器:

3 注意事項
- 查看 cpuprofile 資訊時需要安裝 graphviz
apt install graphviz
4 說明
- 軟體環境
go版本:go1.15.8
作業系統:Ubuntu 20.04 Desktop
Idea:2020.01.04 - 參考檔案
由淺入深掌握Go語言 --慕課網
go 語言編程 --許式偉
graphviz download
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/260645.html
標籤:區塊鏈
上一篇:Solidity進階之路:搭建僵尸工廠 - 第1章: 課程概述
下一篇:第一節 Go的安裝與應用
