【Go】ファイルへの書き込み処理のパフォーマンス改善(
更新日:2025/3/13/(木) 12:38
タグ:Go
概要
DBからレコードを取得してテキストファイルに出力する処理のパフォーマンス改善。
sync.Pool
, writer.WriteString
を使用してメモリ割り当てを抑えてみた。
どのくらいパフォーマンスが向上したのかベンチマークテストも行ってみた。
環境
- MacBook Air M1 メモリ16GB、
- go 1.23.0、PostgreSQL 14.5
計測方法
- Todosテーブルから10,000件のレコードを取得しテキストファイルに出力する
サンプルコード
Before
func BenchmarkBufio(b *testing.B) { db, _ := dbConn() b.ReportAllocs() for i := 0; i < b.N; i++ { FileOutPutTodosWithBufio(db, "file_batched.txt") os.Remove("file_batched.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 }
After
func BenchmarkPool(b *testing.B) { db, _ := dbConn() b.ReportAllocs() for i := 0; i < b.N; i++ { FileOutPutTodosWithPool(db, "file_batched.txt") os.Remove("file_batched.txt") } } var bufWriterPool = &sync.Pool{ New: func() any { return bufio.NewWriter(nil) }, } func getBufWriter(w io.Writer) *bufio.Writer { bufw := bufWriterPool.Get().(*bufio.Writer) bufw.Reset(w) return bufw } func putBufWriter(bufw *bufio.Writer) { bufw.Reset(nil) bufWriterPool.Put(bufw) } // sync.Poolを使用 func FileOutPutTodosWithPool(db *gorm.DB, fileName string) error { file, err := os.Create(fileName) if err != nil { return err } defer file.Close() writer := getBufWriter(file) defer putBufWriter(writer) defer func() { flushErr := writer.Flush() if err == nil { err = flushErr } }() var todos []Todo result := db.Find(&todos) if result.Error != nil { return result.Error } for _, todo := range todos { fields := [][2]string{ {"ID", strconv.FormatInt(int64(todo.ID), 10)}, {"Title", todo.Title}, {"Note", todo.Note}, } _ = writer.WriteByte('{') for i, f := range fields { if i > 0 { _, _ = writer.WriteString(", ") } _, _ = writer.WriteString(f[0]) _, _ = writer.WriteString(": ") _, _ = writer.WriteString(f[1]) } _, err = writer.WriteString("},\n") if err != nil { return err } } return nil }
計測結果
=== RUN BenchmarkBufio BenchmarkBufio-8 60 19,552,349 ns/op 13,212,360 B/op 269,609 allocs/op === RUN BenchmarkPool BenchmarkPool-8 75 16,104,963 ns/op 8,947,325 B/op 179,726 allocs/op
Before(BenchmarkBufio)
- 実行回数: 60回
- 平均実行時間: 19,552,349 ns/op(約19.6ms/1回)
- メモリ使用量: 13,212,360 B/op(約13.2MB/1回)
- メモリアロケーション回数: 269,609回
After(BenchmarkPool)
- 実行回数: 75回
- 平均実行時間: 16,104,963 ns/op(16.1ms/1回)
- メモリ使用量: 8,947,325 B/op(約8.9MB/1回)
- メモリアロケーション回数: 179,726回
比較
Before | After | 比較 | |
---|---|---|---|
実行回数 | 60回 | 75回 | 約1.25倍 増 |
平均実行時間 | 19,552,349 ns/op(約19.6ms/1回) | 16,104,963 ns/op(16.1ms/1回) | 約17.8% 減 |
メモリ使用量 | 13,212,360 B/op(約13.2MB/1回) | 8,947,325 B/op(約8.9MB/1回) | 約32.6% 減 |
メモリアロケーション回数 | 269,609回 | 179,726回 | 約33.3% 減 |
結論
sync.Pool
, writer.WriteString
を使用するとメモリ効率が改善された。
パフォーマンスを気にする場面ではsync.Pool
, writer.WriteString
を使用していきたい。