为什么strings.HasPrefix比bytes.HasPrefix快?

时间:2015-12-01 12:51:21

标签: 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 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倍?

感谢。

1 个答案:

答案 0 :(得分:16)

主要原因是bytes.HasPrefix()strings.HasPrefix()的通话费用不同。正如@tomasz在评论中指出的那样,strings.HashPrefix()默认是内联的,而bytes.HasPrefix()则没有。{/ p>

进一步的原因是不同的参数类型: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是可比较的,而字节切片不是,BenchmarkBytHasPrefix()需要额外的函数调用bytes.Equal()BenchmarkStrHasPrefix()相比(额外的函数调用还包括复制其参数:2个切片标题)。

可能会对原始不同结果产生轻微影响的其他因素:BenchmarkStrHasPrefix()中使用的参数是常量,而BenchmarkBytHasPrefix()中使用的参数是变量。

你不应该担心性能差异,两种功能都只需几纳秒即可完成。

请注意bytes.Equal()的“实施”:

func Equal(a, b []byte) bool // ../runtime/asm_$GOARCH.s

这可能会在某些平台上内联,导致无需额外的通话费用。