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

标签 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 倍的差异?

谢谢。

最佳答案

主要原因是 bytes.HasPrefix() 的通话费用不同和 strings.HasPrefix() .正如@tomasz 在他的评论中指出的那样,strings.HashPrefix() bytes.HasPrefix() 时默认内联不是。

进一步的原因是参数类型不同:bytes.HasPrefix()需要 2 个 slice (2 个 slice 描述符)。 strings.HasPrefix()需要 2 个字符串(2 个字符串标题)。 slice 描述符包含一个指针和 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

内联基准测试中差异很小的原因可能是两个函数都通过 slice string 来测试前缀的存在。和 []byte操作数。自从string s 具有可比性,而字节 slice 则不具有可比性,BenchmarkBytHasPrefix()需要对 bytes.Equal() 进行额外的函数调用相比 BenchmarkStrHasPrefix() (并且额外的函数调用还包括复制其参数:2 个 slice 头)。

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

您不必太担心性能差异,这两个功能只需几纳秒即可完成。

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

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

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

关于string - 为什么 strings.HasPrefix 比 bytes.HasPrefix 快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34020904/

相关文章:

mongodb - 为什么在尝试从mongo集合中获取文档时为什么会出现 “client disconnected”错误?

go - HandleFunc 被调用两次

php - 是否存在 PHP 每功能(或每任务)性能/基准引用?

unit-testing - 我如何在 Go 中编写使用 -short 标志的测试,它可以与 -benchmark 标志结合使用吗?

java - 正则表达式在双引号上分割字符串仅保留引号之间的内容

Python:re.sub仅替换16次

java - 需要帮助格式化 Java 中的字符串

javascript - 在较大的字符串中查找包含给定字母集的最小子字符串

go - 语法错误 : unexpected name, 需要分号或换行符

Python,使用多进程比不使用它慢