为什么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个字符串标题)。切片描述符包含一个指针和2
int
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