【问题标题】:Compare string and byte slice in Go without copy在没有复制的情况下比较 Go 中的字符串和字节切片
【发布时间】:2019-11-09 23:27:20
【问题描述】:

检查 Go 字符串和字节切片是否包含相同字节的最佳方法是什么?最简单的str == string(byteSlice) 效率低,因为它先复制byteSlice

我正在寻找以字符串作为参数的Equal(a, b []byte) 版本,但找不到合适的。

【问题讨论】:

  • 并不是说在 Go 1.5 中“有”strings.Compare,但它的设计效率极低。

标签: go


【解决方案1】:

从 Go 1.5 开始,编译器在使用 stack-allocated temporary 与字符串进行比较时优化字符串(字节)。因此从 Go 1.5 开始

str == string(byteSlice)

成为将字符串与字节切片进行比较的规范且有效的方法。

【讨论】:

  • 即使在 1.7 中,我发现在它们不同的情况下首先比较长度会更快:len(str) == len(byteSlice) && str == string(byteSlice) - 看起来这是因为 Go 创建了临时切片(没有分配,但仍然需要一点时间)才能比较长度。
【解决方案2】:

The Go Programming Language Specification

String types

字符串类型表示字符串值的集合。字符串值是 (可能为空)字节序列。预先声明的字符串类型是 字符串。

字符串 s 的长度(它的字节大小)可以使用 内置函数 len。可以通过整数访问字符串的字节 索引 0 到 len(s)-1。

例如,

package main

import "fmt"

func equal(s string, b []byte) bool {
    if len(s) != len(b) {
        return false
    }
    for i, x := range b {
        if x != s[i] {
            return false
        }
    }
    return true
}

func main() {
    s := "equal"
    b := []byte(s)
    fmt.Println(equal(s, b))
    s = "not" + s
    fmt.Println(equal(s, b))
}

输出:

true
false

【讨论】:

  • 在没有本机实现的情况下,这是足够好的通用替换,尤其是在不期望平均匹配并且循环在第一个或第二个字符之后终止时
【解决方案3】:

如果您对这可能会在以后的版本中中断这一事实感到满意(尽管值得怀疑),您可以使用unsafe

func unsafeCompare(a string, b []byte) int {
    abp := *(*[]byte)(unsafe.Pointer(&a))
    return bytes.Compare(abp, b)
}

func unsafeEqual(a string, b []byte) bool {
    bbp := *(*string)(unsafe.Pointer(&b))
    return a == bbp
}

playground

Benchmarks:

// using:
//  aaa = strings.Repeat("a", 100)
//  bbb = []byte(strings.Repeat("a", 99) + "b")

// go 1.5
BenchmarkCopy-8         20000000                75.4 ns/op
BenchmarkPetersEqual-8  20000000                83.1 ns/op
BenchmarkUnsafe-8       100000000               12.2 ns/op
BenchmarkUnsafeEqual-8  200000000               8.94 ns/op
// go 1.4
BenchmarkCopy           10000000                233  ns/op
BenchmarkPetersEqual    20000000                72.3 ns/op
BenchmarkUnsafe         100000000               15.5 ns/op
BenchmarkUnsafeEqual    100000000               10.7 ns/op

【讨论】:

  • 我认为不应该建议使用 package unsafe 进行过早的优化。如果 OP 能够证明转换是他的代码的主要瓶颈,那么也许吧。
  • @Volker 他解释说,str == string(byteSlice) 对于他的工作量来说效率很低,因为它复制了它,这是可以理解的。
  • 如果它不在他的热门路径上,那么它是低效还是非常低效都没关系。
  • @Volker 很大程度上取决于他想要做什么,我的方法是一个有效的替代方案。
  • 这里的基准假设匹配是预期的,所以当没有什么比手工编码的汇编程序更好时,应该扫描整个字符串。如果人们正在寻找快速的通用解决方案并且可以使用不安全的代码,这仍然是一个有效的答案。
【解决方案4】:

没有理由使用 unsafe 包或其他东西来比较 []bytestring。 Go 编译器现在已经足够聪明了,它可以优化这种转换。

Here's a benchmark:

BenchmarkEqual-8                172135624                6.96 ns/op <--
BenchmarkUnsafe-8               179866616                6.65 ns/op <--
BenchmarkUnsafeEqual-8          175588575                6.85 ns/op <--
BenchmarkCopy-8                 23715144                 47.3 ns/op
BenchmarkPetersEqual-8          24709376                 47.3 ns/op

只需将字节切片转换为字符串并进行比较:

var (
    aaa = strings.Repeat("a", 100)
    bbb = []byte(strings.Repeat("a", 99) + "b")
)

func BenchmarkEqual(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = aaa == string(bbb)
    }
}

?Here is more information about the optimizationthis

【讨论】:

    猜你喜欢
    • 2023-03-18
    • 2010-12-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多