【Go】ファイルへの書き込みをバッファリングに修正してみた
更新日:2025/3/13/(木) 12:38
タグ:Go
概要
DBからレコードを取得してテキストファイルに出力する処理の中でfmt.Fprintf
を使用している箇所をbufio.Writer
のバッファリングを利用するように修正してみた。
どのくらいパフォーマンスが向上したのかベンチマークテストも行ってみた。
環境
- MacBook Air M1 メモリ16GB
- go 1.23.0、PostgreSQL 14.5
計測方法
- Todosテーブルから10,000件のレコードを取得しテキストファイルに出力する
サンプルコード
fmt.Fprintf使用
func BenchmarkFprintf(b *testing.B) { db, _ := dbConn() b.ReportAllocs() for i := 0; i < b.N; i++ { FileOutPutTodosWithFprintf(db, "file_output.txt") os.Remove("file_output.txt") } } func FileOutPutTodosWithFprintf(db *gorm.DB, fileName string) error { file, err := os.Create(fileName) if err != nil { return err } defer file.Close() var todos []Todo result := db.Find(&todos) if result.Error != nil { return result.Error } for _, todo := range todos { fields := []string{ fmt.Sprintf("ID: %v", todo.ID), fmt.Sprintf("Title: %v", todo.Title), fmt.Sprintf("Note: %v", todo.Note), } _, err := fmt.Fprintf(file, "{%s},\n", strings.Join(fields, ", ")) if err != nil { return err } } return nil }
bufio.Writer使用
func BenchmarkBufio(b *testing.B) { db, _ := dbConn() b.ReportAllocs() for i := 0; i < b.N; i++ { FileOutPutTodosWithBufio(db, "file_output.txt") os.Remove("file_output.txt") } } func FileOutPutTodosWithBufio(db *gorm.DB, fileName string) error { file, err := os.Create(fileName) if err != nil { return err } defer file.Close() writer := bufio.NewWriter(file) defer writer.Flush() var todos []Todo result := db.Find(&todos) if result.Error != nil { return result.Error } for _, todo := range todos { fields := []string{ fmt.Sprintf("ID: %v", todo.ID), fmt.Sprintf("Title: %v", todo.Title), fmt.Sprintf("Note: %v", todo.Note), } line := fmt.Sprintf("{%s},\n", strings.Join(fields, ", ")) _, err = writer.Write([]byte(line)) if err != nil { return err } } return nil }
計測結果
=== RUN BenchmarkFprintf BenchmarkFprintf BenchmarkFprintf-8 31 33787055 ns/op 11289062 B/op 249601 allocs/op === RUN BenchmarkBufio BenchmarkBufio BenchmarkBufio-8 61 19502351 ns/op 13212685 B/op 269613 allocs/op
fmt.Fprintf使用(BenchmarkFprintf)
- 実行回数: 31回
- 平均実行時間: 33,787,055 ns/op(約33.8ms/1回)
- メモリ使用量: 11,289,062 B/op(約11.2MB/1回)
- メモリアロケーション回数: 249,601回
bufio.Writer使用(BenchmarkBufio)
- 実行回数: 61回
- 平均実行時間: 19,502,351 ns/op(19.5ms/1回)
- メモリ使用量: 13,212,685 B/op(約13.2MB/1回)
- メモリアロケーション回数: 269,613回
比較
fmt.Fprintf使用 | bufio.Writer使用 | 比較 | |
---|---|---|---|
実行回数 | 31回 | 61回 | 約1.9倍 増 |
平均実行時間 | 33,787,055 ns/op(約33.8ms/1回) | 19,502,351 ns/op(19.5ms/1回) | 約42.2% 減 |
メモリ使用量 | 11,289,062 B/op(約11.2MB/1回) | 13,212,685 B/op(約13.2MB/1回) | 約17.0% 増 |
メモリアロケーション回数 | 249,601回 | 269,613回 | 約8.0% 増 |
パフォーマンスの差の要因
I/O回数がfmt.Fprintf使用にくらべてbufio.Writer使用の方が少ないことが要因か
- fmt.Fprintf使用のI/O回数 → 10,000回(ループの回数分)
- bufio.Writer使用のI/O回数 → 212回(Flushの回数を計測した結果)
結論
- 今回の計測方法ではbufio.Writerのバッファリング処理の方が実行速度いる結果となった
- メモリに関してはfmt.Fprintfを使用したほうが若干優れている結果となった
- バッファリングの場合は内部バッファの追加メモリ確保により、メモリ使用量とアロケーション数が増加した?
- メモリに関してはfmt.Fprintfを使用したほうが若干優れている結果となった
- 処理速度を気にする場面ではバッファリングを使用していきたい