在我的代码中,我有这样的基准:
const STR = "abcd" const PREFIX = "ab" var STR_B = []byte(STR) var PREFIX_B = []byte(PREFIX) func BenchmarkStrHasPrefix(b *testing.B) { for i := 0; i < b.N; i++ { strings.HasPrefix(STR, PREFIX) } } func BenchmarkBytHasPrefix(b *testing.B) { for i := 0; i < b.N; i++ { bytes.HasPrefix(STR_B, PREFIX_B) } }
我对结果有点困惑:
BenchmarkStrHasPrefix-4 300000000 4.67 ns/op BenchmarkBytHasPrefix-4 200000000 8.05 ns/op
为什么差异高达2倍?
谢谢.
其主要原因是在调用的成本差异bytes.HasPrefix()
和strings.HasPrefix()
.正如@tomasz在他的评论中指出的那样,strings.HashPrefix()
默认情况下是内联的,而bytes.HasPrefix()
不是.
进一步的原因是不同的参数类型:bytes.HasPrefix()
采用2个切片(2个切片描述符).strings.HasPrefix()
需要2个字符串(2个字符串标题).切片描述符包含一个指针和2 int
s:长度和容量,请参阅reflect.SliceHeader
.字符串标题只包含一个指针和一个int
:长度,请参阅reflect.StringHeader
.
如果我们手动内联HasPrefix()
基准函数中的函数,我们可以证明这一点,因此我们消除了调用成本(零两者).通过内联它们,不会对它们进行任何函数调用.
HasPrefix()
实现:
// HasPrefix tests whether the byte slice s begins with prefix. func HasPrefix(s, prefix []byte) bool { return len(s) >= len(prefix) && Equal(s[0:len(prefix)], prefix) } // HasPrefix tests whether the string s begins with prefix. func HasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[0:len(prefix)] == prefix }
内联后的基准功能:
func BenchmarkStrHasPrefix(b *testing.B) { s, prefix := STR, PREFIX for i := 0; i < b.N; i++ { _ = len(s) >= len(prefix) && s[0:len(prefix)] == prefix } } func BenchmarkBytHasPrefix(b *testing.B) { s, prefix := STR_B, PREFIX_B for i := 0; i < b.N; i++ { _ = len(s) >= len(prefix) && bytes.Equal(s[0:len(prefix)], prefix) } }
运行这些会得到非常接近的结果:
BenchmarkStrHasPrefix-2 300000000 5.88 ns/op BenchmarkBytHasPrefix-2 200000000 6.17 ns/op
其原因在内联基准小差异可能是这两种功能通过切割测试前缀的存在string
和[]byte
操作数.并且因为string
s是可比较的而字节切片不是,所以BenchmarkBytHasPrefix()
需要额外的函数调用来bytes.Equal()
比较BenchmarkStrHasPrefix()
(并且额外的函数调用还包括制作其参数的副本:2个切片头).
其他可能会对原始不同结果产生轻微影响的事情:使用的参数BenchmarkStrHasPrefix()
是常量,而使用的参数BenchmarkBytHasPrefix()
是变量.
你不应该担心性能差异,这两个功能只需几纳秒即可完成.
注意:"实现" bytes.Equal()
:
func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s
这可以在某些平台中内联,从而不会产生额外的通话费用.