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

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

タグ:Go

概要

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は使用する場所を選びたい
  • ストリーム処理 に修正した場合のパフォーマンスが気になるので、そちらもベンチマークテストを行ってみる