post Image
Golang の文字列連結はどちらが速い?

Go 言語で文字列の連結を行う際にどうやるのが一番速いか,という話。

文字列連結を行う4つの方法

Go 言語で文字列の連結を行う際には概ね以下の4つの方法がある。

  1. +” 演算子で連結する
  2. strings.Join で連結する
  3. bytes.Buffer で追記する
  4. []byte に append する

string 型は「不変(immutable)」なので,最初の2つが高コストになるだろうことはすぐに想像がつく。

では残りの2つはどうなのかというと

によると最後のが一番速いらしい。ほんじゃまぁ,確かめてみるか。

サンプルコードを用意

以下のコードを使って評価してみる。

join.go
package main

import (
    "bufio"
    "bytes"
    "io"
)

//Read content (text data) from buffer
func ContentText(inStream io.Reader) ([]string, error) {
    scanner := bufio.NewScanner(inStream)
    list := make([]string, 0)
    for scanner.Scan() {
        list = append(list, scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        return nil, err
    }
    return list, nil
}

//Write content (text data) to buffer
func WriteBuffer1(lines []string) []byte {
    //write to byte buffer
    content := make([]byte, 0)
    recode := "\r\n"
    for _, line := range lines {
        content = append(content, line...)
        content = append(content, recode...)
    }
    return content
}

//Write content (text data) to buffer
func WriteBuffer1Cap128(lines []string) []byte {
    //write to byte buffer
    content := make([]byte, 0, 128) //128 bytes capacity
    recode := "\r\n"
    for _, line := range lines {
        content = append(content, line...)
        content = append(content, recode...)
    }
    return content
}

//Write content (text data) to buffer
func WriteBuffer1Cap1K(lines []string) []byte {
    //write to byte buffer
    content := make([]byte, 0, 1024) //1K bytes capacity
    recode := "\r\n"
    for _, line := range lines {
        content = append(content, line...)
        content = append(content, recode...)
    }
    return content
}

//Write content (text data) to buffer (buffered I/O)
func WriteBuffer2(lines []string) []byte {
    //write to byte buffer
    content := bytes.NewBuffer(make([]byte, 0))
    recode := "\r\n"
    for _, line := range lines {
        content.WriteString(line)
        content.WriteString(recode)
    }
    return content.Bytes()
}

//Write content (text data) to buffer (buffered I/O)
func WriteBuffer2Cap128(lines []string) []byte {
    //write to byte buffer
    content := bytes.NewBuffer(make([]byte, 0, 128)) //128 bytes capacity
    recode := "\r\n"
    for _, line := range lines {
        content.WriteString(line)
        content.WriteString(recode)
    }
    return content.Bytes()
}

//Write content (text data) to buffer (buffered I/O)
func WriteBuffer2Cap1K(lines []string) []byte {
    //write to byte buffer
    content := bytes.NewBuffer(make([]byte, 0, 1024)) //1K bytes capacity
    recode := "\r\n"
    for _, line := range lines {
        content.WriteString(line)
        content.WriteString(recode)
    }
    return content.Bytes()
}

テストコードはこんな感じ。

join_test.go
package main

import (
    "os"
    "testing"
)

var list []string

func readFile() {
    file, err := os.Open("CollisionsForHashFunctions.txt") //maybe file path
    if err != nil {
        panic(err)
    }
    defer file.Close()
    list, err = ContentText(file)
    if err != nil {
        panic(err)
    }
}

func BenchmarkWriteBuffer1(b *testing.B) {
    readFile()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        content := WriteBuffer1(list)
        _ = content
    }
}

func BenchmarkWriteBuffer1Cap128(b *testing.B) {
    readFile()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        content := WriteBuffer1Cap128(list)
        _ = content
    }
}

func BenchmarkWriteBuffer1Cap1K(b *testing.B) {
    readFile()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        content := WriteBuffer1Cap1K(list)
        _ = content
    }
}

func BenchmarkWriteBuffer2(b *testing.B) {
    readFile()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        content := WriteBuffer2(list)
        _ = content
    }
}

func BenchmarkWriteBuffer2Cap128(b *testing.B) {
    readFile()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        content := WriteBuffer2Cap128(list)
        _ = content
    }
}

func BenchmarkWriteBuffer2Cap1K(b *testing.B) {
    readFile()
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        content := WriteBuffer2Cap1K(list)
        _ = content
    }
}

入力テキストだが,小さいファイルではテストにならない気がしたので,大昔に書いたテキスト CollisionsForHashFunctions.txt を使うことにした。サイズは70行,8KB ほど。

テスト結果

結果は以下のとおり。

C:>go test -bench WriteBuffer -benchmen
testing: warning: no tests to run
PASS
BenchmarkWriteBuffer1-4           100000     12220 ns/op  28864 B/op  11 allocs/op
BenchmarkWriteBuffer1Cap128-4     100000     11620 ns/op  28800 B/op  10 allocs/op
BenchmarkWriteBuffer1Cap1K-4      200000     11605 ns/op  27904 B/op   7 allocs/op
BenchmarkWriteBuffer2-4           100000     14200 ns/op  25568 B/op   9 allocs/op
BenchmarkWriteBuffer2Cap128-4     100000     15790 ns/op  26800 B/op   8 allocs/op
BenchmarkWriteBuffer2Cap1K-4      200000     10305 ns/op  17520 B/op   5 allocs/op
ok      join    13.260s

ありゃりゃ。 bytes.Buffer を使ったほうが速いみたい(capacity を大きくとれば)。

それなら,ファイルサイズを一気に小さくして9行,1KB にしてやってみる。

C:>go test -bench WriteBuffer -benchmen
testing: warning: no tests to run
PASS
BenchmarkWriteBuffer1-4          3000000       443 ns/op    448 B/op   3 allocs/op
BenchmarkWriteBuffer1Cap128-4    5000000       345 ns/op    384 B/op   2 allocs/op
BenchmarkWriteBuffer1Cap1K-4     3000000       561 ns/op   1024 B/op   1 allocs/op
BenchmarkWriteBuffer2-4          1000000      1079 ns/op    544 B/op   4 allocs/op
BenchmarkWriteBuffer2Cap128-4    2000000       727 ns/op    560 B/op   3 allocs/op
BenchmarkWriteBuffer2Cap1K-4     2000000       663 ns/op   1136 B/op   2 allocs/op
ok      join    11.987s

今度は []bytes の方が速くなった。

まぁでも予想通りかな。データのサイズが大きくなればバッファ操作のほうが有利になるのは分かりやすいっちゃあ分かりやすい。注目すべきは BenchmarkWriteBuffer1Cap128BenchmarkWriteBuffer1Cap1K で, capacity を 1KB 取ったほうが若干遅くなっている。この辺のチューニングをどうするか,というところなのだろう。


『 Go 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。