This commit is contained in:
chai2010
2015-12-14 11:31:28 +08:00
parent 44532b45b5
commit 7b4e9340f8
7 changed files with 82 additions and 82 deletions

View File

@@ -1,9 +1,9 @@
## 11.4. 基準測試
准测试是测量一程序在固定工作负载下的性能. 在Go言中, 基准测试函数和普通测试函数类似, 但是以Benchmark为前缀名, 并且带有一 `*testing.B` 型的参数; `*testing.B` 除了提供和 `*testing.T` 似的方法, 还有额外一些和性能量相的方法. 它提供了一个整数N, 用指定操作行的循环次数.
準測試是測量一程序在固定工作負載下的性能. 在Go言中, 基準測試函數和普通測試函數類似, 但是以Benchmark爲前綴名, 併且帶有一 `*testing.B` 型的參數; `*testing.B` 除了提供和 `*testing.T` 似的方法, 還有額外一些和性能量相的方法. 它提供了一箇整數N, 用指定操作行的循環次數.
是 IsPalindrome 函的基准测试, 其中循环将执行N次.
是 IsPalindrome 函的基準測試, 其中循環將執行N次.
```Go
import "testing"
@@ -15,7 +15,7 @@ func BenchmarkIsPalindrome(b *testing.B) {
}
```
用下的命令行基准测试. 和普通测试不同的是, 默认情况下不行任何基准测试. 我需要通 `-bench` 命令行标志参数手工指定要行的基准测试函数. 该参数是一个正则表达式, 用匹配要行的基准测试函数的名字, 默值是空的. 其中 . 模式可以匹配所有基准测试函数, 但是这里总共只有一个基准测试函数, 因此 和 `-bench=IsPalindrome` 参数是等的效果.
用下的命令行基準測試. 和普通測試不衕的是, 默認情況下不行任何基準測試. 我需要通 `-bench` 命令行標誌參數手工指定要行的基準測試函數. 該參數是一箇正則錶達式, 用匹配要行的基準測試函數的名字, 默值是空的. 其中 . 模式可以匹配所有基準測試函數, 但是這裡總共隻有一箇基準測試函數, 因此 和 `-bench=IsPalindrome` 參數是等的效果.
```
$ cd $GOPATH/src/gopl.io/ch11/word2
@@ -25,13 +25,13 @@ BenchmarkIsPalindrome-8 1000000 1035 ns/op
ok gopl.io/ch11/word2 2.179s
```
准测试名的数字后缀部分, 这里是8, 表示运行时对应的 GOMAXPROCS 的值, 这对于一些和并发相关的基准测试是重要的信息.
準測試名的數字後綴部分, 這裡是8, 錶示運行時對應的 GOMAXPROCS 的值, 這對於一些和併發相關的基準測試是重要的信息.
报告显示每次用 IsPalindrome 函数花费 1.035微秒, 是行 1,000,000 次的平均时间. 因为基准测试驱动器并不知道每个基准测试函数运行所花的候, 它会尝试在真正运行基准测试前先尝试用较小的 N 运行测试来估算基准测试函数所需要的时间, 然后推断一个较大的时间保证稳定的测量结果.
報告顯示每次調用 IsPalindrome 函數花費 1.035微秒, 是行 1,000,000 次的平均時間. 因爲基準測試驅動器併不知道每箇基準測試函數運行所花的候, 它會嘗試在眞正運行基準測試前先嘗試用較小的 N 運行測試來估算基準測試函數所需要的時間, 然後推斷一箇較大的時間保証穩定的測量結果.
在基准测试函数内实现, 而不是放在基准测试框架内实现, 这样可以让每个基准测试函数有机会在循环启动前执行初始化代, 这样并不会显著影每次迭代的平均运行时间. 如果还是担心初始化代部分对测量时间带来干扰, 那可以通 testing.B 参数的方法来临时关闭或重置计时器, 不过这些一般很少用到.
在基準測試函數內實現, 而不是放在基準測試框架內實現, 這樣可以讓每箇基準測試函數有機會在循環啟動前執行初始化代, 這樣併不會顯著影每次迭代的平均運行時間. 如果還是擔心初始化代部分對測量時間帶來乾擾, 那可以通 testing.B 參數的方法來臨時關閉或重置計時器, 不過這些一般很少用到.
在我有了一个基准测试和普通测试, 我可以很容易测试新的程序行更快的想法. 也最明显的优化是在 IsPalindrome 函中第二个循环的停止检查, 这样可以避免每个比较都做次:
在我有了一箇基準測試和普通測試, 我可以很容易測試新的程序行更快的想法. 也最明顯的優化是在 IsPalindrome 函中第二箇循環的停止檢査, 這樣可以避免每箇比較都做次:
```Go
n := len(letters)/2
@@ -43,7 +43,7 @@ for i := 0; i < n; i++ {
return true
```
很多情下, 一个明显的优化并不一定就能代码预期的效果. 这个改进在基准测试中值带来了 4% 的性能提.
很多情下, 一箇明顯的優化併不一定就能代碼預期的效果. 這箇改進在基準測試中值帶來了 4% 的性能提.
```
$ go test -bench=.
@@ -52,7 +52,7 @@ BenchmarkIsPalindrome-8 1000000 992 ns/op
ok gopl.io/ch11/word2 2.093s
```
另一个改进想法是在开始为每个字符先分配一个足够大的数组, 这样就可以避免在 append 调用时可能会导致内存的多次重新分配. 明一 letters 数组变量, 指定合适的大小, 像这样,
另一箇改進想法是在開始爲每箇字符先分配一箇足夠大的數組, 這樣就可以避免在 append 調用時可能會導緻內存的多次重新分配. 明一 letters 數組變量, 指定閤適的大小, 像這樣,
```Go
letters := make([]rune, 0, len(s))
@@ -63,7 +63,7 @@ for _, r := range s {
}
```
这个改进提升性能 35%, 报告结果是基 2,000,000 次迭代的平均运行时间统计.
這箇改進提昇性能 35%, 報告結果是基 2,000,000 次迭代的平均運行時間統計.
```
$ go test -bench=.
@@ -72,7 +72,7 @@ BenchmarkIsPalindrome-8 2000000 697 ns/op
ok gopl.io/ch11/word2 1.468s
```
这个例子所示, 快的程序往往是有很少的存分配. `-benchmem` 命令行标志参数将在报告中包含存的分配数据统计. 我可以比较优化前后内存的分配情:
這箇例子所示, 快的程序往往是有很少的存分配. `-benchmem` 命令行標誌參數將在報告中包含存的分配數據統計. 我可以比較優化前後內存的分配情:
```
$ go test -bench=. -benchmem
@@ -80,7 +80,7 @@ PASS
BenchmarkIsPalindrome 1000000 1026 ns/op 304 B/op 4 allocs/op
```
这是优化之后的结果:
這是優化之後的結果:
```
$ go test -bench=. -benchmem
@@ -88,11 +88,11 @@ PASS
BenchmarkIsPalindrome 2000000 807 ns/op 128 B/op 1 allocs/op
```
一次存分配代替多次的存分配省了75%的分配用次数和减少近一半的存需求.
一次存分配代替多次的存分配省了75%的分配調用次數和減少近一半的存需求.
这个基准测试告诉我们所需的绝对时间依赖给定的具操作, 两个不同的操作所需时间的差也是和不同环境相的. 例如, 如果一个函数需要 1ms 理 1,000 元素, 那么处理 10000 或 1百万 将需要多少时间呢? 这样的比揭示了近增长函数的运行时间. 另一例子: I/O 缓存该设置为多大呢? 基准测试可以助我们选择较小的存但能带来满意的性能. 第三例子: 对于一个确定的工作那算法更好? 基准测试可以评估两种不同算法对于相同的输入在不同的场景和负载下的优缺点.
這箇基準測試告訴我們所需的絕對時間依賴給定的具操作, 兩箇不衕的操作所需時間的差也是和不衕環境相的. 例如, 如果一箇函數需要 1ms 理 1,000 元素, 那麼處理 10000 或 1百萬 將需要多少時間呢? 這樣的比揭示了近增長函數的運行時間. 另一例子: I/O 緩存該設置爲多大呢? 基準測試可以助我們選擇較小的存但能帶來滿意的性能. 第三例子: 對於一箇確定的工作那算法更好? 基準測試可以評估兩種不衕算法對於相衕的輸入在不衕的場景和負載下的優缺點.
较基准测试都是结构类似的代. 它通常是用一个参数的函, 从几个标志的基准测试函数入口用, 就像这样:
較基準測試都是結構類似的代. 它通常是用一箇參數的函, 從幾箇標誌的基準測試函數入口調用, 就像這樣:
```Go
func benchmark(b *testing.B, size int) { /* ... */ }
@@ -101,13 +101,13 @@ func Benchmark100(b *testing.B) { benchmark(b, 100) }
func Benchmark1000(b *testing.B) { benchmark(b, 1000) }
```
过函数参数来指定入的大小, 但是参数变量对于每个具体的基准测试都是固定的. 要避免直接改 b.N 来控制输入的大小. 除非你它作为一个固定大小的迭代计算输入, 否则基准测试的结果将毫无意义.
過函數參數來指定入的大小, 但是參數變量對於每箇具體的基準測試都是固定的. 要避免直接改 b.N 來控製輸入的大小. 除非你它作爲一箇固定大小的迭代計算輸入, 否則基準測試的結果將毫無意義.
准测试对于编写代码是很有助的, 但是使工作完成了应应当保存基准测试代码. 因为随着项目的展, 或者是入的增加, 或者是部署到新的操作系统或不同的处理器, 我可以再次用基准测试来帮助我们改进设计.
準測試對於編寫代碼是很有助的, 但是使工作完成了應應噹保存基準測試代碼. 因爲隨着項目的展, 或者是入的增加, 或者是部署到新的操作繫統或不衕的處理器, 我可以再次用基準測試來幫助我們改進設計.
**练习 11.6:** 2.6.2练习 2.4 和 练习 2.5 的 PopCount 函数编写基准测试. 看看基于表格算法在不同情况下的性能.
**練習 11.6:** 2.6.2練習 2.4 和 練習 2.5 的 PopCount 函數編寫基準測試. 看看基於錶格算法在不衕情況下的性能.
**练习 11.7:** *IntSet (§6.5) 的 Add, UnionWith 和 其他方法编写基准测试, 使用大量随机出入. 你可以让这些方法跑多快? 选择字的大小对于性能的影如何? IntSet 和基于内建 map 的实现相比有多快?
**練習 11.7:** *IntSet (§6.5) 的 Add, UnionWith 和 其他方法編寫基準測試, 使用大量隨機齣入. 你可以讓這些方法跑多快? 選擇字的大小對於性能的影如何? IntSet 和基於內建 map 的實現相比有多快?