W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
原文鏈接:https://gopl-zh.github.io/ch11/ch11-04.html
基準(zhǔn)測(cè)試是測(cè)量一個(gè)程序在固定工作負(fù)載下的性能。在Go語(yǔ)言中,基準(zhǔn)測(cè)試函數(shù)和普通測(cè)試函數(shù)寫法類似,但是以Benchmark為前綴名,并且?guī)в幸粋€(gè)*testing.B
類型的參數(shù);*testing.B
參數(shù)除了提供和*testing.T
類似的方法,還有額外一些和性能測(cè)量相關(guān)的方法。它還提供了一個(gè)整數(shù)N,用于指定操作執(zhí)行的循環(huán)次數(shù)。
下面是IsPalindrome函數(shù)的基準(zhǔn)測(cè)試,其中循環(huán)將執(zhí)行N次。
import "testing"
func BenchmarkIsPalindrome(b *testing.B) {
for i := 0; i < b.N; i++ {
IsPalindrome("A man, a plan, a canal: Panama")
}
}
我們用下面的命令運(yùn)行基準(zhǔn)測(cè)試。和普通測(cè)試不同的是,默認(rèn)情況下不運(yùn)行任何基準(zhǔn)測(cè)試。我們需要通過(guò)-bench
命令行標(biāo)志參數(shù)手工指定要運(yùn)行的基準(zhǔn)測(cè)試函數(shù)。該參數(shù)是一個(gè)正則表達(dá)式,用于匹配要執(zhí)行的基準(zhǔn)測(cè)試函數(shù)的名字,默認(rèn)值是空的。其中“.”模式將可以匹配所有基準(zhǔn)測(cè)試函數(shù),但因?yàn)檫@里只有一個(gè)基準(zhǔn)測(cè)試函數(shù),因此和-bench=IsPalindrome
參數(shù)是等價(jià)的效果。
$ cd $GOPATH/src/gopl.io/ch11/word2
$ go test -bench=.
PASS
BenchmarkIsPalindrome-8 1000000 1035 ns/op
ok gopl.io/ch11/word2 2.179s
結(jié)果中基準(zhǔn)測(cè)試名的數(shù)字后綴部分,這里是8,表示運(yùn)行時(shí)對(duì)應(yīng)的GOMAXPROCS的值,這對(duì)于一些與并發(fā)相關(guān)的基準(zhǔn)測(cè)試是重要的信息。
報(bào)告顯示每次調(diào)用IsPalindrome函數(shù)花費(fèi)1.035微秒,是執(zhí)行1,000,000次的平均時(shí)間。因?yàn)榛鶞?zhǔn)測(cè)試驅(qū)動(dòng)器開始時(shí)并不知道每個(gè)基準(zhǔn)測(cè)試函數(shù)運(yùn)行所花的時(shí)間,它會(huì)嘗試在真正運(yùn)行基準(zhǔn)測(cè)試前先嘗試用較小的N運(yùn)行測(cè)試來(lái)估算基準(zhǔn)測(cè)試函數(shù)所需要的時(shí)間,然后推斷一個(gè)較大的時(shí)間保證穩(wěn)定的測(cè)量結(jié)果。
循環(huán)在基準(zhǔn)測(cè)試函數(shù)內(nèi)實(shí)現(xiàn),而不是放在基準(zhǔn)測(cè)試框架內(nèi)實(shí)現(xiàn),這樣可以讓每個(gè)基準(zhǔn)測(cè)試函數(shù)有機(jī)會(huì)在循環(huán)啟動(dòng)前執(zhí)行初始化代碼,這樣并不會(huì)顯著影響每次迭代的平均運(yùn)行時(shí)間。如果還是擔(dān)心初始化代碼部分對(duì)測(cè)量時(shí)間帶來(lái)干擾,那么可以通過(guò)testing.B參數(shù)提供的方法來(lái)臨時(shí)關(guān)閉或重置計(jì)時(shí)器,不過(guò)這些一般很少會(huì)用到。
現(xiàn)在我們有了一個(gè)基準(zhǔn)測(cè)試和普通測(cè)試,我們可以很容易測(cè)試改進(jìn)程序運(yùn)行速度的想法。也許最明顯的優(yōu)化是在IsPalindrome函數(shù)中第二個(gè)循環(huán)的停止檢查,這樣可以避免每個(gè)比較都做兩次:
n := len(letters)/2
for i := 0; i < n; i++ {
if letters[i] != letters[len(letters)-1-i] {
return false
}
}
return true
不過(guò)很多情況下,一個(gè)顯而易見的優(yōu)化未必能帶來(lái)預(yù)期的效果。這個(gè)改進(jìn)在基準(zhǔn)測(cè)試中只帶來(lái)了4%的性能提升。
$ go test -bench=.
PASS
BenchmarkIsPalindrome-8 1000000 992 ns/op
ok gopl.io/ch11/word2 2.093s
另一個(gè)改進(jìn)想法是在開始為每個(gè)字符預(yù)先分配一個(gè)足夠大的數(shù)組,這樣就可以避免在append調(diào)用時(shí)可能會(huì)導(dǎo)致內(nèi)存的多次重新分配。聲明一個(gè)letters數(shù)組變量,并指定合適的大小,像下面這樣,
letters := make([]rune, 0, len(s))
for _, r := range s {
if unicode.IsLetter(r) {
letters = append(letters, unicode.ToLower(r))
}
}
這個(gè)改進(jìn)提升性能約35%,報(bào)告結(jié)果是基于2,000,000次迭代的平均運(yùn)行時(shí)間統(tǒng)計(jì)。
$ go test -bench=.
PASS
BenchmarkIsPalindrome-8 2000000 697 ns/op
ok gopl.io/ch11/word2 1.468s
如這個(gè)例子所示,快的程序往往是伴隨著較少的內(nèi)存分配。-benchmem
命令行標(biāo)志參數(shù)將在報(bào)告中包含內(nèi)存的分配數(shù)據(jù)統(tǒng)計(jì)。我們可以比較優(yōu)化前后內(nèi)存的分配情況:
$ go test -bench=. -benchmem
PASS
BenchmarkIsPalindrome 1000000 1026 ns/op 304 B/op 4 allocs/op
這是優(yōu)化之后的結(jié)果:
$ go test -bench=. -benchmem
PASS
BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op
用一次內(nèi)存分配代替多次的內(nèi)存分配節(jié)省了75%的分配調(diào)用次數(shù)和減少近一半的內(nèi)存需求。
這個(gè)基準(zhǔn)測(cè)試告訴了我們某個(gè)具體操作所需的絕對(duì)時(shí)間,但我們往往想知道的是兩個(gè)不同的操作的時(shí)間對(duì)比。例如,如果一個(gè)函數(shù)需要1ms處理1,000個(gè)元素,那么處理10000或1百萬(wàn)將需要多少時(shí)間呢?這樣的比較揭示了漸近增長(zhǎng)函數(shù)的運(yùn)行時(shí)間。另一個(gè)例子:I/O緩存該設(shè)置為多大呢?基準(zhǔn)測(cè)試可以幫助我們選擇在性能達(dá)標(biāo)情況下所需的最小內(nèi)存。第三個(gè)例子:對(duì)于一個(gè)確定的工作哪種算法更好?基準(zhǔn)測(cè)試可以評(píng)估兩種不同算法對(duì)于相同的輸入在不同的場(chǎng)景和負(fù)載下的優(yōu)缺點(diǎn)。
比較型的基準(zhǔn)測(cè)試就是普通程序代碼。它們通常是單參數(shù)的函數(shù),由幾個(gè)不同數(shù)量級(jí)的基準(zhǔn)測(cè)試函數(shù)調(diào)用,就像這樣:
func benchmark(b *testing.B, size int) { /* ... */ }
func Benchmark10(b *testing.B) { benchmark(b, 10) }
func Benchmark100(b *testing.B) { benchmark(b, 100) }
func Benchmark1000(b *testing.B) { benchmark(b, 1000) }
練習(xí) 11.6: 為2.6.2節(jié)的練習(xí)2.4和練習(xí)2.5的PopCount函數(shù)編寫基準(zhǔn)測(cè)試??纯椿诒砀袼惴ㄔ诓煌闆r下對(duì)提升性能會(huì)有多大幫助。
練習(xí) 11.7: 為*IntSet
(§6.5)的Add、UnionWith和其他方法編寫基準(zhǔn)測(cè)試,使用大量隨機(jī)輸入。你可以讓這些方法跑多快?選擇字的大小對(duì)于性能的影響如何?IntSet和基于內(nèi)建map的實(shí)現(xiàn)相比有多快?
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: