golang1.9 ReuseRecordでCSVのREADパフォーマンスが向上するかも!?
golangを1.9にアップデートしてみました。
Go 1.9 Release Notes - The Go Programming Language
release noteを見ると機能の追加からパフォーマンスの向上まで色々ありますが、
今回は、CSVのREAD性能が向上したというので、ベンチマークを取ってどのくらい改善されたのか確認します。
検証概要
encoding/csv
The new field Reader.ReuseRecord controls whether calls to Read may return a slice sharing the backing array of the previous call’s returned slice for improved performance.
掻い摘んでいうと、ReuseRecord
というプロパティをTRUEにするとSliceを共有することで、メモリアロケーションを減らしてパーフォーマンスもアップするよという内容。
早速、ReuseRecord
がFALSEの場合とTRUEの場合を比較してみる。
サンプルデータの準備
まずは、性能検証に読込するCSVを用意します。
とりあえず、ランダムな文字列で150,000行のサンプルデータを用意します。
下のようなサンプルCSV生成コードをmain.goという名前で保存します。
package main import ( "encoding/csv" "flag" "math/rand" "os" "strconv" "golang.org/x/text/encoding/japanese" "golang.org/x/text/transform" ) var rs1Letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func RandString1(n int) string { b := make([]rune, n) for i := range b { b[i] = rs1Letters[rand.Intn(len(rs1Letters))] } return string(b) } func main() { flag.Parse() file, err := os.Create(flag.Arg(0)) if err != nil { return } defer file.Close() writer := csv.NewWriter(transform.NewWriter(file, japanese.ShiftJIS.NewEncoder())) writer.UseCRLF = true i := 1 for { if i > 150000 { break } var new_record []string new_record = append(new_record, strconv.Itoa(i)) new_record = append(new_record, RandString1(12)) new_record = append(new_record, RandString1(20)) new_record = append(new_record, RandString1(6)) new_record = append(new_record, RandString1(9)) new_record = append(new_record, RandString1(10)) new_record = append(new_record, RandString1(20)) new_record = append(new_record, RandString1(5)) new_record = append(new_record, RandString1(6)) new_record = append(new_record, RandString1(13)) new_record = append(new_record, RandString1(15)) writer.Write(new_record) i++ } writer.Flush() }
下記で実行します。
go run main.go sample.csv
実行と同ディレクトリに、sample.csvというファイルが出来上がります。
これを使って性能検証を行ってみます。
ベンチマーク
benchmark用に以下のファイルをCsvReuseRecord_test.goとして用意します。
package main import ( "encoding/csv" "io" "os" "testing" ) func BenchmarkCsvDisableReuseRecord(b *testing.B) { file, err := os.Open("./sample.csv") if err != nil { return } defer file.Close() b.ResetTimer() reader := csv.NewReader(file) reader.LazyQuotes = true reader.ReuseRecord = false for { record, err := reader.Read() if err == io.EOF { break } s := make([]byte, 0) for _, v := range record { s = append(s, v...) } } } func BenchmarkCsvEnableReuseRecord(b *testing.B) { file, err := os.Open("./sample.csv") if err != nil { return } defer file.Close() b.ResetTimer() reader := csv.NewReader(file) reader.LazyQuotes = true reader.ReuseRecord = true for { record, err := reader.Read() if err == io.EOF { break } s := make([]byte, 0) for _, v := range record { s = append(s, v...) } } }
早速、ベンチマーク取ってみる。
$ go test -bench . -benchmem PASS BenchmarkCsvDisableReuseRecord-4 2000000000 0.26 ns/op 0 B/op 0 allocs/op BenchmarkCsvEnableReuseRecord-4 2000000000 0.23 ns/op 0 B/op 0 allocs/op ok 17.239s
うーん、目に見えて改善というのは自分の端末ではわからなかったが、何度か実行してみると、かなり結果がぶれていた。
実行時のIOにも左右されているのだと思うが、利用する際には実際に実行する環境でベンチを取って判断すると良いと思います。
ちなみに、ReuseRecord
はデフォルトではFALSEになっています。