【问题标题】:Performance comparison for concatenation of strings [duplicate]字符串连接的性能比较[重复]
【发布时间】:2020-06-26 12:29:45
【问题描述】:

我正在尝试找出 strings.Join 方法+= 的常规连接的用法。

对于这个比较,我在 os.Args 上使用这两种方法作为要连接的字符串列表。

我的连接函数代码是:

func regular_concatenation() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = " "
    }
    fmt.Println(s)
}

func join_concatenation() {
    fmt.Println(strings.Join(os.Args, " "))
}

而性能检查的主要功能是:

func main() {
    var end_1, end_2 float64
    var start_1, start_2 time.Time

    start_1 = time.Now()
    for i:=0; i < 100; i++ {
        ex1_3_join_concatenation()
    }
    end_1 = time.Since(start_1).Seconds()

    start_2 = time.Now()
    for i:=0; i < 100; i++ {
        ex1_3_regular_concatenation()
    }
    end_2 = time.Since(start_2).Seconds()

    fmt.Println(end_1)
    fmt.Println(end_2)
}

问题是 - 当我运行代码时,比如使用 20 个参数 (os.Args),我得到的结果是 strings.Join 方法比常规连接慢

这让我很困惑,因为我理解它的方式 - 当使用常规 += 方法时,它每次都会创建一个新的字符串引用(因为字符串在 golang 中是不可变的),因此垃圾收集器应该按顺序运行收集未使用的数据,这很浪费时间。

所以问题是 - strings.Join 真的是更快的方法吗?如果是 - 在这个例子中我做错了什么?

【问题讨论】:

  • 从创建一个真实的、独立的基准开始;糟糕的基准会产生糟糕的结果。如果没有其他混淆变量,join 方法将具有更少的分配,并且会更快。您的 join 函数还处理一个比另一个多的参数。
  • 你必须在谷歌上搜索有关如何对 go 代码进行基准测试的信息,我认为这里没什么好讨论的
  • 好吧,我建议你问这个基准有什么问题。因为你的简单问题有一个简单的答案,strings.Join 比字符串连接更快。
  • 一个不同之处可能是您在第二个基准测试中添加了 os.Args[0]。
  • 尝试使用包含 1000 个左右元素的数组代替 os.Args,您会发现 strings.Join 每次都更快。

标签: performance go garbage-collection


【解决方案1】:

由于各种编译器优化,字符串连接可以非常有效,但在您的情况下,我发现strings.Join 更快(请参阅下面的代码基准)。

一般建议使用strings.Builder 来构建字符串。见How to efficiently concatenate strings in go

顺便说一句,您应该使用 Go 附带的出色的基准测试工具。只需将这些函数放在以_test.go 结尾的文件中(例如string_test.go)并运行go test -bench=.

func BenchmarkConcat(b *testing.B) { // 132 ns/op
    ss := []string {"sadsadsa", "dsadsakdas;k", "8930984"}
    for i := 0; i < b.N; i++ {
        var s, sep string
        for j := 0; j < len(ss); j++ {
            s += sep + ss[j]
            sep = " "
        }
        _ = s
    }
}

func BenchmarkJoin(b *testing.B) {  // 56.7 ns/op
    ss := []string {"sadsadsa", "dsadsakdas;k", "8930984"}
    for i := 0; i < b.N; i++ {
        s := strings.Join(ss, " ")
        _ = s
    }
}

func BenchmarkBuilder(b *testing.B) { // 58.5
    ss := []string {"sadsadsa", "dsadsakdas;k", "8930984"}
    for i := 0; i < b.N; i++ {
        var s strings.Builder
        // Grow builder to expected max length (maybe this
        // needs to be calculated dep. on your requirements)
        s.Grow(32)
        var sep string
        for j := 0; j < len(ss); j++ {
            s.WriteString(ss[j])
            s.WriteString(sep)
            sep = " "
        }
        _ = s.String()
    }
}

【讨论】:

  • 没有什么可以基准的。连接显式地同时为每个字符串分配。Join 使用的是预分配的缓冲区。在 1-2 个元素连接的情况下,由于工作量较少,Ofc 可能会更快。
  • 不需要使用构建器,因为它是 strings.Join 的作用。在已知大小的情况下,如果需要快速,我会使用 make([]byte, size)copy
  • 如果没有什么可以进行基准测试的,我在“时间”测试中使用的代码没有显示您所说的想要的结果,这很奇怪(无论哪种方式,strings.Join 都更快) ,不是吗?
  • @Laevus Dexter 确定创建自己的缓冲区然后使用copy 可能会快一点,但您应该提供代码以便我们对其进行基准测试。就我个人而言,我怀疑它比strings.Join 快得多,并且使用已知良好的标准库函数总是更好。顺便说一句,我不明白你所说的“没有什么可以基准”是什么意思,除非你的意思是它是如此明显,哪个更快没有意义。
  • 确定我赞成这个答案,因为它确实是有用的信息,但我不知道为什么要回答他的真实问题 - 如果 join 应该更快,为什么在他的情况下(没有基准)它不是?
猜你喜欢
  • 2021-10-13
  • 1970-01-01
  • 2016-03-08
  • 2014-06-05
  • 2011-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
相关资源
最近更新 更多