当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么strings.HasPrefix比bytes.HasPrefix快?》经验,为你挑选了1个好方法。

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

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> icza..:

其主要原因是在调用的成本差异bytes.HasPrefix()strings.HasPrefix().正如@tomasz在他的评论中指出的那样,strings.HashPrefix()默认情况下是内联的,而bytes.HasPrefix()不是.

进一步的原因是不同的参数类型:bytes.HasPrefix()采用2个切片(2个切片描述符).strings.HasPrefix()需要2个字符串(2个字符串标题).切片描述符包含一个指针和2 ints:长度和容量,请参阅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操作数.并且因为strings是可比较的而字节切片不是,所以BenchmarkBytHasPrefix()需要额外的函数调用来bytes.Equal()比较BenchmarkStrHasPrefix()(并且额外的函数调用还包括制作其参数的副本:2个切片头).

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

你不应该担心性能差异,这两个功能只需几纳秒即可完成.

注意:"实现" bytes.Equal():

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

这可以在某些平台中内联,从而不会产生额外的通话费用.


将`-gcflags -m`传递给`go run`将告诉你内联的内容 - 它只是`strings.HasPrefix`,而不是`bytes.HasPrefix`.如果禁用内联(`-gcflags -l`),您将获得可比较的结果(字节慢〜10%).但是,如果你一起禁用优化(`-gcflags -N`)基准测试将有利于比较字节.至少在我的i5上.说到这一点,差异似乎在于汇编魔术.
推荐阅读
贴进你的心聆听你的世界
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有