mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-08-04 15:01:46 +00:00
make
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
### 11.2.1. 隨機測試
|
||||
|
||||
|
||||
表格驱动的测试便于构造基于精心挑选的测试数据的测试用例. 另一种测试思路是随机测试, 也就是通过构造更广泛的随机输入来测试探索函数的行为.
|
||||
錶格驅動的測試便於構造基於精心挑選的測試數據的測試用例. 另一種測試思路是隨機測試, 也就是通過構造更廣汎的隨機輸入來測試探索函數的行爲.
|
||||
|
||||
那么对于一个随机的输入, 我们如何能知道希望的输出结果呢? 这里有两种策略. 第一个是编写另一个函数, 使用简单和清晰的算法, 虽然效率较低但是行为和要测试的函数一致, 然后针对相同的随机输入检查两者的输出结果. 第二种是生成的随机输入的数据遵循特定的模式, 这样我们就可以知道期望的输出的模式.
|
||||
那麼對於一箇隨機的輸入, 我們如何能知道希望的輸齣結果呢? 這裡有兩種策略. 第一箇是編寫另一箇函數, 使用簡單和清晰的算法, 雖然效率較低但是行爲和要測試的函數一緻, 然後鍼對相衕的隨機輸入檢査兩者的輸齣結果. 第二種是生成的隨機輸入的數據遵循特定的模式, 這樣我們就可以知道期望的輸齣的模式.
|
||||
|
||||
下面的例子使用的是第二种方法: randomPalindrome 函数用于随机生成回文字符串.
|
||||
下麫的例子使用的是第二種方法: randomPalindrome 函數用於隨機生成迴文字符串.
|
||||
|
||||
```Go
|
||||
import "math/rand"
|
||||
@@ -39,13 +39,13 @@ func TestRandomPalindromes(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
虽然随机测试有不确定因素, 但是它也是至关重要的, 我们可以从失败测试的日志获取足够的信息. 在我们的例子中, 输入 IsPalindrome 的 p 参数将告诉我们真实的数据, 但是对于函数将接受更复杂的输入, 不需要保存所有的输入, 只要日志中简单地记录随机数种子即可(像上面的方式). 有了这些随机数初始化种子, 我们可以很容易修改测试代码以重现失败的随机测试.
|
||||
雖然隨機測試有不確定因素, 但是它也是至關重要的, 我們可以從失敗測試的日誌穫取足夠的信息. 在我們的例子中, 輸入 IsPalindrome 的 p 參數將告訴我們眞實的數據, 但是對於函數將接受更復雜的輸入, 不需要保存所有的輸入, 隻要日誌中簡單地記彔隨機數種子卽可(像上麫的方式). 有了這些隨機數初始化種子, 我們可以很容易脩改測試代碼以重現失敗的隨機測試.
|
||||
|
||||
通过使用当前时间作为随机种子, 在整个过程中的每次运行测试命令时都将探索新的随机数据. 如果你使用的是定期运行的自动化测试集成系统, 随机测试将特别有价值.
|
||||
通過使用噹前時間作爲隨機種子, 在整箇過程中的每次運行測試命令時都將探索新的隨機數據. 如果你使用的是定期運行的自動化測試集成繫統, 隨機測試將特別有價值.
|
||||
|
||||
**练习 11.3:** TestRandomPalindromes 只测试了回文字符串. 编写新的随机测试生成器, 用于测试随机生成的非回文字符串.
|
||||
**練習 11.3:** TestRandomPalindromes 隻測試了迴文字符串. 編寫新的隨機測試生成器, 用於測試隨機生成的非迴文字符串.
|
||||
|
||||
**练习 11.4:** 修改 randomPalindrome 函数, 以探索 IsPalindrome 对标点和空格的处理.
|
||||
**練習 11.4:** 脩改 randomPalindrome 函數, 以探索 IsPalindrome 對標點和空格的處理.
|
||||
|
||||
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
## 11.2. 測試函數
|
||||
|
||||
|
||||
每个测试函数必须导入 testing 包. 测试函数有如下的签名:
|
||||
每箇測試函數必須導入 testing 包. 測試函數有如下的簽名:
|
||||
|
||||
```Go
|
||||
func TestName(t *testing.T) {
|
||||
@@ -9,7 +9,7 @@ func TestName(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
测试函数的名字必须以Test开头, 可选的后缀名必须以大写字母开头:
|
||||
測試函數的名字必須以Test開頭, 可選的後綴名必須以大寫字母開頭:
|
||||
|
||||
```Go
|
||||
func TestSin(t *testing.T) { /* ... */ }
|
||||
@@ -17,7 +17,7 @@ func TestCos(t *testing.T) { /* ... */ }
|
||||
func TestLog(t *testing.T) { /* ... */ }
|
||||
```
|
||||
|
||||
其中 t 参数用于报告测试失败和附件的日志信息. 让我们顶一个一个实例包 gopl.io/ch11/word1, 只有一个函数 IsPalindrome 用于检查一个字符串是否从前向后和从后向前读都一样. (这个实现对于一个字符串是否是回文字符串前后重复测试了两次; 我们稍后会再讨论这个问题.)
|
||||
其中 t 參數用於報告測試失敗和附件的日誌信息. 讓我們頂一箇一箇實例包 gopl.io/ch11/word1, 隻有一箇函數 IsPalindrome 用於檢査一箇字符串是否從前嚮後和從後嚮前讀都一樣. (這箇實現對於一箇字符串是否是迴文字符串前後重復測試了兩次; 我們稍後會再討論這箇問題.)
|
||||
|
||||
```Go
|
||||
gopl.io/ch11/word1
|
||||
@@ -36,7 +36,7 @@ func IsPalindrome(s string) bool {
|
||||
}
|
||||
```
|
||||
|
||||
在相同的目录下, word_test.go 文件包含了 TestPalindrome 和 TestNonPalindrome 两个测试函数. 每一个都是测试 IsPalindrome 是否给出正确的结果, 并使用 t.Error 报告失败:
|
||||
在相衕的目彔下, word_test.go 文件包含了 TestPalindrome 和 TestNonPalindrome 兩箇測試函數. 每一箇都是測試 IsPalindrome 是否給齣正確的結果, 併使用 t.Error 報告失敗:
|
||||
|
||||
```Go
|
||||
package word
|
||||
@@ -59,7 +59,7 @@ func TestNonPalindrome(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
`go test` (或 `go build`) 命令 如果没有参数指定包那么将默认采用当前目录对应的包. 我们可以用下面的命令构建和运行测试.
|
||||
`go test` (或 `go build`) 命令 如果沒有參數指定包那麼將默認寀用噹前目彔對應的包. 我們可以用下麫的命令構建和運行測試.
|
||||
|
||||
```
|
||||
$ cd $GOPATH/src/gopl.io/ch11/word1
|
||||
@@ -67,7 +67,7 @@ $ go test
|
||||
ok gopl.io/ch11/word1 0.008s
|
||||
```
|
||||
|
||||
还比较满意, 我们运行了这个程序, 不过没有提前退出是因为还没有遇到BUG报告. 一个法国名为 Noelle Eve Elleon 的用户抱怨 IsPalindrome 函数不能识别 ‘‘été.’’. 另外一个来自美国中部用户的抱怨是不能识别 ‘‘A man, a plan, a canal: Panama.’’. 执行特殊和小的BUG报告为我们提供了新的更自然的测试用例.
|
||||
還比較滿意, 我們運行了這箇程序, 不過沒有提前退齣是因爲還沒有遇到BUG報告. 一箇法國名爲 Noelle Eve Elleon 的用戶抱怨 IsPalindrome 函數不能識別 ‘‘été.’’. 另外一箇來自美國中部用戶的抱怨是不能識別 ‘‘A man, a plan, a canal: Panama.’’. 執行特殊和小的BUG報告爲我們提供了新的更自然的測試用例.
|
||||
|
||||
```Go
|
||||
func TestFrenchPalindrome(t *testing.T) {
|
||||
@@ -84,9 +84,9 @@ func TestCanalPalindrome(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
为了避免两次输入较长的字符串, 我们使用了提供了有类似 Printf 格式化功能的 Errorf 函数来汇报错误结果.
|
||||
爲了避免兩次輸入較長的字符串, 我們使用了提供了有類似 Printf 格式化功能的 Errorf 函數來彙報錯誤結果.
|
||||
|
||||
当添加了这两个测试用例之后, `go test` 返回了测试失败的信息.
|
||||
噹添加了這兩箇測試用例之後, `go test` 返迴了測試失敗的信息.
|
||||
|
||||
```
|
||||
$ go test
|
||||
@@ -98,11 +98,11 @@ FAIL
|
||||
FAIL gopl.io/ch11/word1 0.014s
|
||||
```
|
||||
|
||||
先编写测试用例并观察到测试用例触发了和用户报告的错误相同的描述是一个好的测试习惯. 只有这样, 我们才能定位我们要真正解决的问题.
|
||||
先編寫測試用例併觀察到測試用例觸發了和用戶報告的錯誤相衕的描述是一箇好的測試習慣. 隻有這樣, 我們纔能定位我們要眞正解決的問題.
|
||||
|
||||
先写测试用例的另好处是, 运行测试通常会比手工描述报告的处理更快, 这让我们可以进行快速地迭代. 如果测试集有很多运行缓慢的测试, 我们可以通过只选择运行某些特定的测试来加快测试速度.
|
||||
先寫測試用例的另好處是, 運行測試通常會比手工描述報告的處理更快, 這讓我們可以進行快速地迭代. 如果測試集有很多運行緩慢的測試, 我們可以通過隻選擇運行某些特定的測試來加快測試速度.
|
||||
|
||||
参数 `-v` 用于打印每个测试函数的名字和运行时间:
|
||||
參數 `-v` 用於打印每箇測試函數的名字和運行時間:
|
||||
|
||||
```
|
||||
$ go test -v
|
||||
@@ -121,7 +121,7 @@ exit status 1
|
||||
FAIL gopl.io/ch11/word1 0.017s
|
||||
```
|
||||
|
||||
参数 `-run` 是一个正则表达式, 只有测试函数名被它正确匹配的测试函数才会被 `go test` 运行:
|
||||
參數 `-run` 是一箇正則錶達式, 隻有測試函數名被它正確匹配的測試函數纔會被 `go test` 運行:
|
||||
|
||||
```
|
||||
$ go test -v -run="French|Canal"
|
||||
@@ -137,11 +137,11 @@ FAIL gopl.io/ch11/word1 0.014s
|
||||
```
|
||||
|
||||
|
||||
当然, 一旦我们已经修复了失败的测试用例, 在我们提交代码更新之前, 我们应该以不带参数的 `go test` 命令运行全部的测试用例, 以确保更新没有引入新的问题.
|
||||
噹然, 一旦我們已經脩復了失敗的測試用例, 在我們提交代碼更新之前, 我們應該以不帶參數的 `go test` 命令運行全部的測試用例, 以確保更新沒有引入新的問題.
|
||||
|
||||
我们现在的任务就是修复这些错误. 简要分析后发现第一个BUG的原因是我们采用了 byte 而不是 rune 序列, 所以像 "été" 中的 é 等非 ASCII 字符不能正确处理. 第二个BUG是因为没有忽略空格和字母的大小写导致的.
|
||||
我們現在的任務就是脩復這些錯誤. 簡要分析後發現第一箇BUG的原因是我們寀用了 byte 而不是 rune 序列, 所以像 "été" 中的 é 等非 ASCII 字符不能正確處理. 第二箇BUG是因爲沒有忽略空格和字母的大小寫導緻的.
|
||||
|
||||
针对上述两个BUG, 我们仔细重写了函数:
|
||||
鍼對上述兩箇BUG, 我們仔細重寫了函數:
|
||||
|
||||
```Go
|
||||
gopl.io/ch11/word2
|
||||
@@ -168,7 +168,7 @@ func IsPalindrome(s string) bool {
|
||||
}
|
||||
```
|
||||
|
||||
同时我们也将之前的所有测试数据合并到了一个测试中的表格中.
|
||||
衕時我們也將之前的所有測試數據閤併到了一箇測試中的錶格中.
|
||||
|
||||
```Go
|
||||
func TestIsPalindrome(t *testing.T) {
|
||||
@@ -198,24 +198,24 @@ func TestIsPalindrome(t *testing.T) {
|
||||
}
|
||||
```
|
||||
|
||||
我们的新测试阿都通过了:
|
||||
我們的新測試阿都通過了:
|
||||
|
||||
```
|
||||
$ go test gopl.io/ch11/word2
|
||||
ok gopl.io/ch11/word2 0.015s
|
||||
```
|
||||
|
||||
这种表格驱动的测试在Go中很常见的. 我们很容易想表格添加新的测试数据, 并且后面的测试逻辑也没有冗余, 这样我们可以更好地完善错误信息.
|
||||
這種錶格驅動的測試在Go中很常見的. 我們很容易想錶格添加新的測試數據, 併且後麫的測試邏輯也沒有冗餘, 這樣我們可以更好地完善錯誤信息.
|
||||
|
||||
失败的测试的输出并不包括调用 t.Errorf 时刻的堆栈调用信息. 不像其他语言或测试框架的 assert 断言, t.Errorf 调用也没有引起 panic 或停止测试的执行. 即使表格中前面的数据导致了测试的失败, 表格后面的测试数据依然会运行测试, 因此在一个测试中我们可能了解多个失败的信息.
|
||||
失敗的測試的輸齣併不包括調用 t.Errorf 時刻的堆棧調用信息. 不像其他語言或測試框架的 assert 斷言, t.Errorf 調用也沒有引起 panic 或停止測試的執行. 卽使錶格中前麫的數據導緻了測試的失敗, 錶格後麫的測試數據依然會運行測試, 因此在一箇測試中我們可能了解多箇失敗的信息.
|
||||
|
||||
如果我们真的需要停止测试, 或许是因为初始化失败或可能是早先的错误导致了后续错误等原因, 我们可以使用 t.Fatal 或 t.Fatalf 停止测试. 它们必须在和测试函数同一个 goroutine 内调用.
|
||||
如果我們眞的需要停止測試, 或許是因爲初始化失敗或可能是早先的錯誤導緻了後續錯誤等原因, 我們可以使用 t.Fatal 或 t.Fatalf 停止測試. 它們必須在和測試函數衕一箇 goroutine 內調用.
|
||||
|
||||
测试失败的信息一般的形式是 "f(x) = y, want z", f(x) 解释了失败的操作和对应的输出, y 是实际的运行结果, z 是期望的正确的结果. 就像前面检查回文字符串的例子, 实际的函数用于 f(x) 部分. 如果显示 x 是表格驱动型测试中比较重要的部分, 因为同一个断言可能对应不同的表格项执行多次. 要避免无用和冗余的信息. 在测试类似 IsPalindrome 返回布尔类型的函数时, 可以忽略并没有额外信息的 z 部分. 如果 x, y 或 z 是 y 的长度, 输出一个相关部分的简明总结即可. 测试的作者应该要努力帮助程序员诊断失败的测试.
|
||||
測試失敗的信息一般的形式是 "f(x) = y, want z", f(x) 解釋了失敗的操作和對應的輸齣, y 是實際的運行結果, z 是期望的正確的結果. 就像前麫檢査迴文字符串的例子, 實際的函數用於 f(x) 部分. 如果顯示 x 是錶格驅動型測試中比較重要的部分, 因爲衕一箇斷言可能對應不衕的錶格項執行多次. 要避免無用和冗餘的信息. 在測試類似 IsPalindrome 返迴佈爾類型的函數時, 可以忽略併沒有額外信息的 z 部分. 如果 x, y 或 z 是 y 的長度, 輸齣一箇相關部分的簡明總結卽可. 測試的作者應該要努力幫助程序員診斷失敗的測試.
|
||||
|
||||
**练习 11.1:** 为 4.3节 中的 charcount 程序编写测试.
|
||||
**練習 11.1:** 爲 4.3節 中的 charcount 程序編寫測試.
|
||||
|
||||
**练习 11.2:** 为 (§6.5)的 IntSet 编写一组测试, 用于检查每个操作后的行为和基于内置 map 的集合等价 , 后面 练习11.7 将会用到.
|
||||
**練習 11.2:** 爲 (§6.5)的 IntSet 編寫一組測試, 用於檢査每箇操作後的行爲和基於內置 map 的集閤等價 , 後麫 練習11.7 將會用到.
|
||||
|
||||
|
||||
{% include "./ch11-02-1.md" %}
|
||||
|
@@ -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 的實現相比有多快?
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user