いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-228 (効率的に文字列を結合する)(Go Collective)

概要

Stackoverflowには Go Collective というのがあります。

stackoverflow.com

The official Q&A channel for Google's Go Programming Language.

と書いてある通り、公式のQ&Aとなっていて、いい質問と回答が揃っています。

ついでなので、自分の勉強も兼ねて、ここにメモメモ。。。

今回はコレ。

stackoverflow.com

サンプル

main.go

// Stackoverflow Go Collective example
//
// How to efficiently concatenate strings in go
//
// URL
//   - https://stackoverflow.com/questions/1760757/how-to-efficiently-concatenate-strings-in-go
//
// REFERENCES
//   - https://pkg.go.dev/strings@latest#Builder
//   - https://yourbasic.org/golang/measure-execution-time/
package main

import (
    "bytes"
    "log"
    "strings"
    "time"

    "github.com/devlights/gomy/times"
)

func init() {
    log.SetFlags(0)
}

func main() {
    // Go 1.10 より strings.Builder が追加された。
    // bytes.Buffer でも良いが、文字列の場合はこちらのほうが効率が良い。

    var (
        sb  strings.Builder
        buf bytes.Buffer
    )

    // ----- bytes.Buffer ----- //
    elapsed := times.Stopwatch(func(start time.Time) {
        for i := 0; i < 3_000_000; i++ {
            buf.WriteString("i")
        }
    })
    log.Printf("[bytes.Buffer   ] len=%d, elapsed=%v", buf.Len(), elapsed)

    // ----- strings.Builder ----- //
    elapsed = times.Stopwatch(func(start time.Time) {
        for i := 0; i < 3_000_000; i++ {
            sb.WriteString("i")
        }
    })
    log.Printf("[strings.Builder] len=%d, elapsed=%v", sb.Len(), elapsed)
}

Taskfile.yml

version: "3"

tasks:
  bench:
    cmds:
      - go test -benchmem -run=^$ -bench .
  run:
    cmds:
      - go run -race main.go

実行すると以下のようになります。

gitpod /workspace/try-golang (master) $ task -d examples/gocollective/concatenate-strings/ run
task: [run] go run -race main.go
[bytes.Buffer   ] len=3000000, elapsed=671.430265ms
[strings.Builder] len=3000000, elapsed=575.146634ms

確かに strings.Builder の方が僅かに速いですね。

bench_test.go

ベンチ取れよって思ったので、ベンチマークも書きました。

package main

import (
    "bytes"
    "strings"
    "testing"
)

func BenchmarkBufferConcatenate(b *testing.B) {
    var (
        buf bytes.Buffer
    )

    b.StartTimer()
    for i := 0; i < b.N; i++ {
        buf.WriteString("i")
    }
    b.StopTimer()

    b.Log(buf.Len())
}

func BenchmarkStringBuilderConcatenate(b *testing.B) {
    var (
        sb strings.Builder
    )

    b.StartTimer()
    for i := 0; i < b.N; i++ {
        sb.WriteString("i")
    }
    b.StopTimer()

    b.Log(sb.Len())
}

実行すると以下のようになります。

gitpod /workspace/try-golang (master) $ task -d examples/gocollective/concatenate-strings/ bench
task: [bench] go test -benchmem -run=^$ -bench .
goos: linux
goarch: amd64
pkg: github.com/devlights/try-golang/examples/gocollective/concatenate-strings
cpu: AMD EPYC 7B13
BenchmarkBufferConcatenate-16                   100000000               12.28 ns/op            2 B/op          0 allocs/op
--- BENCH: BenchmarkBufferConcatenate-16
    bench_test.go:20: 1
    bench_test.go:20: 100
    bench_test.go:20: 10000
    bench_test.go:20: 1000000
    bench_test.go:20: 100000000
BenchmarkStringBuilderConcatenate-16            142488498                7.035 ns/op           5 B/op          0 allocs/op
--- BENCH: BenchmarkStringBuilderConcatenate-16
    bench_test.go:34: 1
    bench_test.go:34: 100
    bench_test.go:34: 10000
    bench_test.go:34: 1000000
    bench_test.go:34: 100000000
    bench_test.go:34: 142488498
PASS
ok      github.com/devlights/try-golang/examples/gocollective/concatenate-strings       3.125s

同じ傾向ですね。

参考情報


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。