概要
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を使用したほうが若干優れている結果となった
- 処理速度を気にする場面ではバッファリングを使用していきたい