为什么strings.HasPrefix比bytes.HasPrefix快? [英] Why strings.HasPrefix is faster than bytes.HasPrefix?

查看:48
本文介绍了为什么strings.HasPrefix比bytes.HasPrefix快?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的代码中,我有这样的基准:

  const STR ="abcd"const PREFIX ="ab"var STR_B = [] byte(STR)var PREFIX_B = [] byte(PREFIX)func BenchmarkStrHasPrefix(b * testing.B){对于我:= 0;我<b.N;我++ {strings.HasPrefix(STR,PREFIX)}}func BenchmarkBytHasPrefix(b * testing.B){对于我:= 0;我<b.N;我++ {bytes.HasPrefix(STR_B,PREFIX_B)}} 

我对结果有点困惑:

  BenchmarkStrHasPrefix-4 300000000 4.67 ns/opBenchmarkBytHasPrefix-4 200000000 8.05 ns/op 

为什么相差最多2倍?

谢谢.

解决方案

主要原因是 strings.HasPrefix() .正如@tomasz在他的评论中指出的那样,默认情况下,内联了 strings.HashPrefix(),而没有嵌入 bytes.HasPrefix().

进一步的原因是不同的参数类型: bytes.HasPrefix()占用2片(2片描述符). strings.HasPrefix()接受2个字符串(2个字符串标题).切片描述符包含一个指针和2个 int :长度和容量,请参见 reflect.SliceHeader .字符串标头仅包含一个指针和一个 int :长度,请参见 Reflection.StringHeader .

如果我们在基准函数中手动内嵌 HasPrefix()函数,我们可以证明这一点,因此可以消除调用成本(两者均为零).通过内联它们,不会对它们进行任何函数调用.

HasPrefix()实现:

 //HasPrefix测试字节片s是否以前缀开头.func HasPrefix(s,前缀[] byte)bool {返回len> = len(前缀)&&等于(s [0:len(前缀)],前缀)}//HasPrefix测试字符串s是否以前缀开头.func HasPrefix(s,前缀字符串)bool {返回len> = len(前缀)&&s [0:len(prefix)] ==前缀} 

内联基准功能后:

  func BenchmarkStrHasPrefix(b * testing.B){s,前缀:= STR,PREFIX对于我:= 0;我<b.N;我++ {_ = len> = len(prefix)&&s [0:len(prefix)] ==前缀}}func BenchmarkBytHasPrefix(b * testing.B){s,前缀:= STR_B,PREFIX_B对于我:= 0;我<b.N;我++ {_ = len> = len(prefix)&&bytes.Equal(s [0:len(prefix)],前缀)}} 

运行这些将获得非常接近的结果:

  BenchmarkStrHasPrefix-2 300000000 5.88 ns/opBenchmarkBytHasPrefix-2 200000000 6.17 ns/op 

内联基准测试之所以有小的差异,可能是因为这两个函数都通过切片 string [] byte 操作数来测试前缀的存在.而且,由于 string 是可比较的,而字节切片却没有,因此 BenchmarkBytHasPrefix()需要对 bytes.Equal() 的实现" :

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

在某些平台上可能会内联,因此不会产生额外的通话费用.

In my code, I have such benchmark:

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)
    }
}

And I am little bit confused about results:

BenchmarkStrHasPrefix-4    300000000    4.67 ns/op
BenchmarkBytHasPrefix-4    200000000    8.05 ns/op

Why there is up to 2x difference?

Thanks.

解决方案

The main reason is the difference in calling cost of bytes.HasPrefix() and strings.HasPrefix(). As @tomasz pointed out in his comment, strings.HashPrefix() is inlined by default while bytes.HasPrefix() is not.

Further reason is the different parameter types: bytes.HasPrefix() takes 2 slices (2 slice descriptors). strings.HasPrefix() takes 2 strings (2 string headers). Slice descriptors contain a pointer and 2 ints: length and capacity, see reflect.SliceHeader. String headers contain only a pointer and an int: the length, see reflect.StringHeader.

We can prove this if we manually inline the HasPrefix() functions in the benchmark functions, so we eliminate the calling costs (zero both). By inlining them, no function call will be made (to them).

HasPrefix() implementations:

// 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
}

Benchmark functions after inlining them:

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)
    }
}

Running these you get very close results:

BenchmarkStrHasPrefix-2 300000000                5.88 ns/op
BenchmarkBytHasPrefix-2 200000000                6.17 ns/op

The reason for the small difference in the inlined benchmarks may be that both functions test the presence of the prefix by slicing the string and []byte operand. And since strings are comparable while byte slices are not, BenchmarkBytHasPrefix() requires an additional function call to bytes.Equal() compared to BenchmarkStrHasPrefix() (and the extra function call also includes making copies of its parameters: 2 slice headers).

Other things that may slightly contribute to the original different results: arguments used in BenchmarkStrHasPrefix() are constants, while parameters used in BenchmarkBytHasPrefix() are variables.

You shouldn't worry much about the performance difference, both functions complete in just a few nanoseconds.

Note that the "implementation" of bytes.Equal():

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

This may be inlined in some platforms resulting in no additional call costs.

这篇关于为什么strings.HasPrefix比bytes.HasPrefix快?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆