【Go】ファイルへの書き込み処理のパフォーマンス改善(

更新日:2025/3/13/(木) 12:38

タグ:Go

概要

DBからレコードを取得してテキストファイルに出力する処理のパフォーマンス改善。

sync.Pool , writer.WriteString を使用してメモリ割り当てを抑えてみた。

どのくらいパフォーマンスが向上したのかベンチマークテストも行ってみた。

環境

計測方法

サンプルコード

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)

After(BenchmarkPool)

比較

BeforeAfter比較
実行回数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 を使用していきたい。