为什么 strings.HasPrefix 比 bytes.HasPrefix 快?
Why strings.HasPrefix is faster than bytes.HasPrefix?
在我的代码中,我有这样的基准:
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()
and strings.HasPrefix()
的通话费用不同。正如@tomasz 在他的评论中指出的那样,默认情况下 strings.HashPrefix()
是内联的,而 bytes.HasPrefix()
不是。
进一步的原因是不同的参数类型:bytes.HasPrefix()
需要 2 个切片(2 个切片描述符)。 strings.HasPrefix()
需要 2 个字符串(2 个字符串 headers)。切片描述符包含一个指针和 2 int
s:长度和容量,参见 reflect.SliceHeader
. String headers contain only a pointer and an int
: the length, see 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
是可比较的,而字节片则不是,因此与 BenchmarkStrHasPrefix()
相比,BenchmarkBytHasPrefix()
需要对 bytes.Equal()
进行额外的函数调用(并且额外的函数调用还包括复制它的参数:2 slice headers).
其他可能对原始不同结果略有影响的因素:BenchmarkStrHasPrefix()
中使用的参数是常量,而 BenchmarkBytHasPrefix()
中使用的参数是变量。
您不必太担心性能差异,这两个功能只需几纳秒即可完成。
注意bytes.Equal()
的"implementation":
func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s
这可能会在某些平台中内联,从而不会产生额外的调用费用。
在我的代码中,我有这样的基准:
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()
and strings.HasPrefix()
的通话费用不同。正如@tomasz 在他的评论中指出的那样,默认情况下 strings.HashPrefix()
是内联的,而 bytes.HasPrefix()
不是。
进一步的原因是不同的参数类型:bytes.HasPrefix()
需要 2 个切片(2 个切片描述符)。 strings.HasPrefix()
需要 2 个字符串(2 个字符串 headers)。切片描述符包含一个指针和 2 int
s:长度和容量,参见 reflect.SliceHeader
. String headers contain only a pointer and an int
: the length, see 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
是可比较的,而字节片则不是,因此与 BenchmarkStrHasPrefix()
相比,BenchmarkBytHasPrefix()
需要对 bytes.Equal()
进行额外的函数调用(并且额外的函数调用还包括复制它的参数:2 slice headers).
其他可能对原始不同结果略有影响的因素:BenchmarkStrHasPrefix()
中使用的参数是常量,而 BenchmarkBytHasPrefix()
中使用的参数是变量。
您不必太担心性能差异,这两个功能只需几纳秒即可完成。
注意bytes.Equal()
的"implementation":
func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s
这可能会在某些平台中内联,从而不会产生额外的调用费用。