1. 程式人生 > >Hyperledger fabric效能測試及分析

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
    的值,通常等於CPU物理執行緒數;
  • 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

title

此外,還可以使用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

title

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大部分的工作都是在進行加解密和校驗,如橢圓曲線演算法。

title