📝

【Go】reflectパッケージを使用した処理のベンチマークテスト

2024/11/30に公開

概要

DBからレコードを取得してテキストファイルに出力する処理の中でreflectパッケージを使っていたが、パフォーマンス的によろしくないという情報が多かったで使わないようにした。

実際にどのくらいパフォーマンスが違うのか気になったのでベンチマークテストを行ってみた。

環境

  • MacBook Air M1 メモリ16GB、
  • go 1.23.0、PostgreSQL 14.5

計測方法

  • Todosテーブルから10,000件のレコードを取得しテキストファイルに出力する

サンプルコード

reflectパッケージ使用

func BenchmarkFileOutPutTodosWithRefrect(b *testing.B) {
	db, _ := dbConn()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		FileOutPutTodosWithRefrect(db, "file_output.txt")
		os.Remove("file_output.txt")
	}
}

func FileOutPutTodosWithRefrect(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 {
		val := reflect.ValueOf(todo)
		typ := val.Type()

		var fields []string
		for i := 0; i < typ.NumField(); i++ {
			key := typ.Field(i).Name
			value := fmt.Sprintf("%v", val.Field(i).Interface())
			fields = append(fields, fmt.Sprintf("%v: %v", key, value))
		}
		_, err := fmt.Fprintf(file, "{%s},\n", strings.Join(fields, ", "))
		if err != nil {
			return err
		}
	}

	return nil
}

reflectパッケージ不使用

func BenchmarkFileOutPutTodos(b *testing.B) {
	db, _ := dbConn()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		FileOutPutTodos(db, "file_output.txt")
		os.Remove("file_output.txt")
	}
}

func FileOutPutTodos(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
}

計測結果

=== RUN   BenchmarkFileOutPutTodosWithRefrect
BenchmarkFileOutPutTodosWithRefrect
BenchmarkFileOutPutTodosWithRefrect-8 18  63923287 ns/op  24169018 B/op  669901 allocs/op

=== RUN   BenchmarkFileOutPutTodos
BenchmarkFileOutPutTodos
BenchmarkFileOutPutTodos-8            34  33159001 ns/op  11290029 B/op  249605 allocs/op

reflectパッケージ使用(BenchmarkFileOutPutTodosWithRefrect)

  • 実行回数: 18回
  • 平均実行時間: 63,923,287 ns/op(約63.9ms/1回)
  • メモリ使用量: 24,169,018 B/op(約24MB/1回)
  • メモリアロケーション回数: 669,901回

reflectパッケージ不使用(BenchmarkFileOutPutTodos)

  • 実行回数: 34回
  • 平均実行時間: 33,159,001 ns/op(33.5ms/1回)
  • メモリ使用量: 11,290,029 B/op(約11MB/1回)
  • メモリアロケーション回数: 249,605回

比較

reflect使用 reflect不使用 比較
実行回数 18回 34回 約1.9倍 増
平均実行時間 63,923,287 ns/op(約63.9ms/1回) 33,159,001 ns/op(33.5ms/1回) 約48.1% 減
メモリ使用量 24,169,018 B/op(約24MB/1回) 11,290,029 B/op(約11MB/1回 約53.3% 減
メモリアロケーション回数 669,901回 249,605回 約62.7% 減

結論

  • 今回の計測方法ではreflect使用しない方が実行速度、メモリ効率共に優れている結果となった
  • reflectは使用する場所を選びたい
  • ストリーム処理 に修正した場合のパフォーマンスが気になるので、そちらもベンチマークテストを行ってみる
GitHubで編集を提案

Discussion