Compare two string slices in GoLang
開發中經常會遇到需要比較兩個slice包含的元素是否完全相等的情況,一般來說有兩個思路:
-
reflect
比較的方法 - 循環遍歷比較的方法
這里用檢查兩個字符串slice是否相等的例子來測試一下這兩種思路的效率<strike>我當然知道你知道reflect方法效率更差啦</strike>
reflect比較的方法
func StringSliceReflectEqual(a, b []string) bool {
return reflect.DeepEqual(a, b)
}
這個寫法很簡單,就是直接使用reflect
包的reflect.DeepEqual
方法來比較a
和b
是否相等
這是我最初完成這個需求的方式,年輕嘛,比較天真,覺得reflect
啊,高端大氣,而且一行代碼搞定,簡潔有力,給自己默默的點個贊
循環遍歷比較的方法
func StringSliceEqual(a, b []string) bool {
if len(a) != len(b) {
return false
}
if (a == nil) != (b == nil) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
以上是我們項目中的使用的用來比較字符串slice是否相等的一個函數,代碼邏輯很簡單;先比較長度是否相等,否
則false
;再比較兩個slice是否都為nil或都不為nil,否
則false
;再比較對應索引處兩個slice的元素是否相等,否
則false
;前面都為是
則true
需要注意
if (a == nil) != (b == nil) {
return false
}
這段代碼是必須的,雖然如果沒有這段代碼,在大多數情況下,上面的函數可以正常工作,但是增加這段代碼的作用是與reflect.DeepEqual
的結果保持一致:[]int{} != []int(nil)
Benchmark測試效率
我們都知道Golang中reflect效率很低,所以雖然循環遍歷的方法看起來很啰嗦,但是如果真的效率比reflect方法高很多,就只能忍痛放棄reflect了
使用Benchmark來簡單的測試下二者的效率
Benchmark StringSliceEqual
func BenchmarkEqual(b *testing.B) {
sa := []string{"q", "w", "e", "r", "t"}
sb := []string{"q", "w", "a", "s", "z", "x"}
b.ResetTimer()
for n := 0; n < b.N; n++ {
StringSliceEqual(sa, sb)
}
}
Benchmark StringSliceReflectEqual
func BenchmarkDeepEqual(b *testing.B) {
sa := []string{"q", "w", "e", "r", "t"}
sb := []string{"q", "w", "a", "s", "z", "x"}
b.ResetTimer()
for n := 0; n < b.N; n++ {
StringSliceReflectEqual(sa, sb)
}
}
上面兩個函數中,b.ResetTimer()
一般用于準備時間比較長的時候重置計時器減少準備時間帶來的誤差,這里可用可不用
在測試文件所在目錄執行go test -bench=.
命令
在我的電腦,使用循環遍歷的方式,3.43納秒完成一次比較;使用reflect的方式,208納米完成一次操作,效率對比十分明顯
BCE優化
Golang提供BCE特性,即Bounds-checking elimination,關于Golang中的BCE,推薦一篇大牛博客Bounds Check Elimination (BCE) In Golang 1.7
func StringSliceEqualBCE(a, b []string) bool {
if len(a) != len(b) {
return false
}
if (a == nil) != (b == nil) {
return false
}
b = b[:len(a)]
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
上述代碼通過b = b[:len(a)]
處的bounds check能夠明確保證v != b[i]
中的b[i]
不會出現越界錯誤,從而避免了b[i]
中的越界檢查從而提高效率
類似的,完成Benchmark函數
func BenchmarkEqualBCE(b *testing.B) {
sa := []string{"q", "w", "e", "r", "t"}
sb := []string{"q", "w", "a", "s", "z", "x"}
b.ResetTimer()
for n := 0; n < b.N; n++ {
StringSliceEqualBCE(sa, sb)
}
}
在測試文件所在目錄執行go test -bench=.
命令
看起來提升并不明顯啊,而且在運行多次Benchmard測試的過程中還出現過BenchmarkEqualBCE
效率低于BenchmarkEqual
的情況(╯‵□′)╯︵┴─┴
可能是我對BCE的理解姿勢有問題亦或是Golang BCE自身的問題,總之這個如果我有了更深入的理解會再次更新
但是隨著Golang的優化,應該會越來越明顯吧 ┬─┬ ノ( ' - 'ノ)
結論
推薦使用StringSliceEqualBCE形式的比較函數<strike>反正不用reflect比較就不會被領導罵被同事噴</strike>
代碼已經上傳到了我的Github倉庫可以下載測試
最后當然是希望喜歡這篇文章的朋友為我的個人博客增加點人氣啦