Hyperledger fabric效能測試及分析
1 Go語言效能測試
寫效能測試在Go語言中是很便捷的,go自帶的標準工具鏈就有完善的支援。
1.1 benchmark
寫benchmark測試有如下約定:
- benchmark也是測試,因此也是以
_test.go
結尾的檔案; - 需要
import testing
; - 測試方法以
Benchmark
開始,並且擁有一個*testing.B
引數。
*testing.B
引數提供了大多數和*testing.T
類似的方法,同時也額外增加了一些效能測試相關的方法。此外,還提供了一個整型成員N
,用來指定被檢測操作的執行次數。
舉個例子,下邊是一個連線字串的操作:
bench_test.go
package main
import "testing"
import "strings"
func BenchmarkStringJoin1(b *testing.B) {
input := []string{"Hello", "World"}
for i := 0; i < b.N; i++ {
result := strings.Join(input, " ")
if result != "Hello World" {
b.Error("Unexpected result: " + result)
}
}
}
然後執行benchmark測試:
$ go test -bench=. -run=NONE
-bench
可以根據正則表示式篩選要測試的方法,這裡的.
表示匹配所有的測試方法;-run=NONE
表示忽略功能測試。
輸出如下:
goos: linux
goarch: amd64
pkg: hello/bench_test
BenchmarkStringJoin1-8 50000000 36.6 ns/op
PASS
ok hello/bench_test 1.871s
BenchmarkStringJoin1-8
表示測試了哪個方法,8
標識GOMAXPROCS
50000000
即進行了50000000次測試,基準測試執行器開始並不知道這個操作耗時長短,所以開始的時候它使用一個比較小的N值,然後根據執行情況推算出合適的N值;36.6 ns/op
表示每個操作耗時36.6納秒。
如果希望報告更多資訊,可以通過增加引數實現,如通過如下命令展示記憶體分配資訊:
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: hello/bench_test
BenchmarkStringJoin1-8 30000000 37.2 ns/op 16 B/op 1 allocs/op
PASS
ok hello/bench_test 1.159s
16 B/op
表示每次操作需要16位元組的記憶體;1 allocs/op
表示每次操作會進行一次記憶體分配。
當然,也可以在測試程式碼中指定(這樣就不需要-benchmem
引數了):
func BenchmarkStringJoin1(b *testing.B) {
b.ReportAllocs() // *testing.B的ReportAllocs方法指定報告記憶體分配資訊
input := []string{"Hello", "World"}
for i := 0; i < b.N; i++ {
result := strings.Join(input, " ")
if result != "Hello World" {
b.Error("Unexpected result: " + result)
}
}
}
對於以上測試方法,優化如下(增加如下方法):
func BenchmarkStringJoin2(b *testing.B) {
b.ReportAllocs()
input := []string{"Hello", "World"}
join := func(strs []string, delim string) string {
if len(strs) == 2 {
return strs[0] + delim + strs[1];
}
return "";
};
for i := 0; i < b.N; i++ {
result := join(input, " ")
if result != "Hello World" {
b.Error("Unexpected result: " + result)
}
}
}
$ go test -bench=.
goos: linux
goarch: amd64
pkg: hello/bench_test
BenchmarkStringJoin1-8 50000000 36.5 ns/op 16 B/op 1 allocs/op
BenchmarkStringJoin2-8 100000000 20.2 ns/op 0 B/op 0 allocs/op
PASS
ok hello/bench_test 3.909s
可見,優化後效能有較大提升,記憶體使用成本也有明顯降低。
1.2 profiling
更多時候,我們不僅希望瞭解執行時長和記憶體佔用情況,而是希望進行效能剖析,尋找關鍵程式碼優化點。Go語言使用pprof工具來進行效能剖析。
Go通過對執行過程進行取樣,來獲取profiling資料。具體來說支援如下幾種效能指標:
- CPU profiling:用於識別出執行過程中需要CPU最多的函式。在每個CPU上面執行的執行緒每個幾毫秒進行定期的中斷,並在每次中斷過程中記錄一個性能剖析事件,然後恢復執行。
- heap profiling:用於識別出負責分配最多記憶體的語句。
- block profiling:用於識別出阻塞協程最久的操作,比如系統呼叫、通道傳送、接收資料和獲取鎖等,效能分析庫會在goroutine被這些操作阻塞的時候記錄一個事件。
命令如下:
$ go test -bench . -cpuprofile=cpu.out -blockprofile=block.out -memprofile=mem.out
這個時候該pprof出馬了:
$ go tool pprof -text ./bench_test.test cpu.out
File: bench_test.test
Type: cpu
Time: Jul 26, 2018 at 10:17pm (CST)
Duration: 4.10s, Total samples = 3.94s (96.01%)
Showing nodes accounting for 3.85s, 97.72% of 3.94s total
Dropped 40 nodes (cum <= 0.02s)
flat flat% sum% cum cum%
1.63s 41.37% 41.37% 2.93s 74.37% runtime.concatstrings
0.55s 13.96% 55.33% 0.68s 17.26% runtime.mallocgc
0.34s 8.63% 63.96% 0.34s 8.63% runtime.memmove
0.33s 8.38% 72.34% 2.01s 51.02% hello/bench_test.BenchmarkStringJoin2
0.24s 6.09% 78.43% 3.17s 80.46% runtime.concatstring3
0.23s 5.84% 84.26% 0.96s 24.37% runtime.rawstringtmp
0.21s 5.33% 89.59% 1.82s 46.19% hello/bench_test.BenchmarkStringJoin1
0.12s 3.05% 92.64% 1.61s 40.86% strings.Join
0.05s 1.27% 93.91% 0.05s 1.27% runtime.memclrNoHeapPointers
0.05s 1.27% 95.18% 0.73s 18.53% runtime.rawstring
0.02s 0.51% 95.69% 0.02s 0.51% runtime.(*mspan).nextFreeIndex
... ...
可以看到佔用CPU時間最多的函式。
還可以輸出為圖片格式(不過需要先安裝GraphViz,如sudo apt install graphviz
):
go tool pprof -svg ./bench_test.test cpu.out > cpu.svg
此外,還可以使用uber/go-torch
這個庫生成火焰圖,這個庫使用了FlameGraph。準備工作如下:
git clone --depth=1 https://github.com/brendangregg/FlameGraph.git ~/.flamegraph
export PATH=$PATH:~/.flamegraph
go get github.com/uber/go-torch
然後生成火焰圖:
go-torch -b cpu.out -f cpu.torch.svg
2 fabric效能剖析
fabric效能剖析基於net/http/pprof包,使用這個包可以讓pprof執行在一個web介面上。
fabric的peer內建有profile server,預設時執行在6060埠上的,並且預設關閉。可以通過將/etc/hyperledger/fabric/core.yaml
中的peer.profile.enabled
設定為true
來啟用,或者設定環境變數CORE_PEER_PROFILE_ENABLED=true
。
這裡我們藉助caliper專案進行測試。
在caliper專案的測試環境的docker-compose.yaml檔案中配置環境變數CORE_PEER_PROFILE_ENABLED=true
。仍然以smallbank
的測試為例,則在network/fabric/simplenetwork/docker-compose.yaml下增加一個environment變數。
然後啟動效能測試,在Caliper專案根目錄下執行:
node benchmark/smallbank/main.js
在環境準備好之後,測試開始之初,開啟另一個終端,檢視一下peer節點的IP(如docker inspect $(docker ps | grep " peer0.org1.example.com" | awk '{print $1}')
命令),然後執行如下命令,收集profiling資料:
# 預設採集30秒資料
go tool pprof http://<peer-ip>:6060/debug/pprof/profile
# 定義資料採集時長
go tool pprof http://<peer-ip>:6060/debug/pprof/profile
這個命令會預設採集30秒的資料,並進入互動模式,採集的資料預設的會在~/pprof
下建立名字是pprof.XXX.samples.cpu.NNN.pb.gz
的檔案。
在互動模式下,分別鍵入web
指令、svg
指令、top10
指令等,可以生成報告或圖片。
然後生成火焰圖,更加直觀:
go-torch -b pprof.peer.samples.cpu.001.pb.gz -f peer.cpu.torch.svg
可以發現,CPU大部分的工作都是在進行加解密和校驗,如橢圓曲線演算法。