1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go】ファイルへの書き込み処理のパフォーマンス改善(sync.Pool , writer.WriteString の使用)

Last updated at Posted at 2024-12-27

概要

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?