概要
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
を使用していきたい。