为什么strings.HasPrefix比bytes.HasPrefix快?
在我的代码中,我有这样的基准:为什么strings.HasPrefix比bytes.HasPrefix快?,string,go,benchmarking,slice,String,Go,Benchmarking,Slice,在我的代码中,我有这样的基准: 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 BenchmarkB
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)
}
}
为什么会有高达2倍的差异
谢谢。主要原因是和的通话费用不同。正如@tomasz在他的评论中指出的那样,
strings.HashPrefix()
默认是内联的,而bytes.HasPrefix()
不是内联的
另一个原因是参数类型不同:bytes.HasPrefix()
需要两个片段(两个片段描述符)<代码>字符串。HasPrefix()接受2个字符串(2个字符串标题)。切片描述符包含一个指针和2int
s:长度和容量,请参阅。字符串头仅包含指针和int
:长度,请参阅
如果我们在基准函数中手动内联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)
}
}
内联基准测试中存在微小差异的原因可能是这两个函数都通过切片字符串
和[]字节
操作数来测试前缀的存在。由于string
s具有可比性,而字节片则不具有可比性,BenchmarkBytHasPrefix()
需要一个额外的函数调用来与BenchmarkStrHasPrefix()
进行比较(额外的函数调用还包括复制其参数:2个片头)
其他可能对原始结果产生轻微影响的因素:在BenchmarkStrHasPrefix()
中使用的参数是常量,而在BenchmarkBytHasPrefix()
中使用的参数是变量
您不必太担心性能差异,这两个函数只需几纳秒即可完成
请注意,“实施”以下各项:
这可能在某些平台中内联,不会产生额外的调用成本。STR、
stru B
、PREFIX
、PREFIX_B
?@IsmailBadawi更新示例)注意,在我的Core2Duo P8600上,两个基准都执行相同的操作(~20 ns/op),而在i5-2400上,它们与您的类似–5.5 vs 8.5 ns/ops这还不够解释:如果插入额外的非内联函数调用,则速度仍然较慢。遗憾的是,我不明白汇编中的相等函数实际上做了什么。有人能解释吗?@0x434D53+1,例如,在asm_amd64.s(第1312行(运行时·eqstring)和第1652行(字节·相等))中,代码有什么区别?@0x434D53做了进一步的调查,请参阅编辑后的答案。将-gcflags-m
传递到go run
将告诉您什么是内联的–它只是字符串。HasPrefix
,而不是字节。HasPrefix
。如果您禁用内联(-gcflags-l
),您将获得类似的结果(字节数大约慢10%)。但是,如果同时禁用优化(-gcflags-N
),基准测试将有利于比较字节。至少在我15岁的时候。这么说,区别似乎在于编译的魔力。@0x434D53我也没有研究过,但我的猜测是,比较字符串常量,它在编译时处理,比较非常量字符串,长度比较是内联的。更多细节见附件。
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
func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s