当优化软件时字符串比较可能和你想的有些不同。特别是包括拆分跨 goroutines 的循环, 找到一个更快的哈希算法,或者一些听起来更科学的方式。当我们做出这样的修改时,会有一种成就感。然而, 字符串比较通常是信息传递中(in a pipeline)的最大瓶颈。下面的代码段经常被使用,但它是最糟糕的解决方案 (参见下面的 benchmarks),并导致了实际问题。
strings.ToLower(name) == strings.ToLower(othername)
ToLower==ToLower
ToLower
strings.ToLower
// Pseudo code
func CompareInsensitive(a, b string) bool {
// loop over string a and convert every rune to lowercase
for i := 0; i < len(a); i++ { a[i] = unicode.ToLower(a[i]) }
// loop over string b and convert every rune to lowercase
for i := 0; i < len(b); i++ { b[i] = unicode.ToLower(b[i]) }
// loop over both a and b and return false if there is a mismatch
for i := 0; i < len(a); i++ {
if a[i] != b[i] {
return false
}
}
return true
}
nlen(a) + len(b) + len(a)
CompareInsensitive("fizzbuzz", "buzzfizz")
unicode.ToLower(a[0])unicode.ToLower(b[0])
CompareInsensitive
// Pseudo code
func CompareInsensitive(a, b string) bool {
// a quick optimization. If the two strings have a different
// length then they certainly are not the same
if len(a) != len(b) {
return false
}
for i := 0; i < len(a); i++ {
// if the characters already match then we don't need to
// alter their case. We can continue to the next rune
if a[i] == b[i] {
continue
}
if unicode.ToLower(a[i]) != unicode.ToLower(b[i]) {
// the lowercase characters do not match so these
// are considered a mismatch, break and return false
return false
}
}
// The string length has been traversed without a mismatch
// therefore the two match
return true
}
新函数效率更高。上限是一个字符串的长度而不是两个字符串的长度和。怎么看我们上面的比较?循环比较最多只有 8 次。甚至,如果第一个字符不同,那就只循环一次。我们优化使得比较操作减少了大约 20 倍!
stringsstrings.EqualFold
性能测试
// When both strings are equal
BenchmarkEqualFoldBothEqual-8 20000000 124 ns/op
BenchmarkToLowerBothEqual-8 10000000 339 ns/op
// When both strings are equal until the last rune
BenchmarkEqualFoldLastRuneNotEqual-8 20000000 129 ns/op
BenchmarkToLowerLastRuneNotEqual-8 10000000 346 ns/op
// When both strings are distinct
BenchmarkEqualFoldFirstRuneNotEqual-8 300000000 11.2 ns/op
BenchmarkToLowerFirstRuneNotEqual-8 10000000 333 ns/op
// When both strings have a different case at rune 0
BenchmarkEqualFoldFirstRuneDifferentCase-8 20000000 125 ns/op
BenchmarkToLowerFirstRuneDifferentCase-8 10000000 433 ns/op
// When both strings have a different case in the middle
BenchmarkEqualFoldMiddleRuneDifferentCase-8 20000000 123 ns/op
BenchmarkToLowerMiddleRuneDifferentCase-8 10000000 428 ns/op
EqualFold
这很重要么?
你可能认为 400 纳秒并不重要。大多数情况下你可能是对的。不管怎样,一些微小的优化处理像其他的处理一样简单。在这个例子中,要比原来的处理方式更简单。合格的工程师在日常工作中就会使用这些微小的优化处理方式。他们不会等到变成问题的时候才去优化软件,他们从开始的时候就写出优化的软件。就算是最优秀的工程师也不可能从 0 开始写出最优化的软件。不可能凭空想象出每个极端的案例然后优化它。并且,在我们提供给用户软件的时候,我们也无法预知用户的行为。不管怎样,在日常工作中加入这些简单的处理方式有益于延长软件的生命周期,预防将来可能不必要的瓶颈。就算那个瓶颈没什么影响,你也不会浪费你的付出。
本文由 原创编译, 荣誉推出