Golang Reflection:遅いのか?
Takashi Yamamoto
Infrastructure Engineer · Leapcell

Reflectionが必要な理由
まず、リフレクションがもたらす利点を理解する必要があります。もし利点が何もなければ、実際にはそれを使う必要はなく、パフォーマンスへの影響を心配する必要もありません。
Go言語におけるリフレクションの実装原理
Go言語は構文要素が少なく、設計がシンプルなため、特に強力な表現力を持っていません。しかし、Go言語のreflect
パッケージは、その構文的な欠点を補うことができます。リフレクションは繰り返しのコーディング作業を減らすことができ、ツールキットはリフレクションを使って異なる構造体の入力パラメータを処理します。
リフレクションを使って構造体が空かどうかを判断する
ビジネスシナリオ
このようにして、入力された構造体が空の場合、SQLを連結せずに直接リターンすることで、フルテーブルスキャンや遅いSQLを避けることができます。
リフレクションを使わない実装
リフレクションを使わない場合、構造体が空かどうかを判断する必要があるとき、各フィールドを一つずつチェックする必要があります。実装は以下のようになります。
type aStruct struct { Name string Male string } func (s *aStruct) IsEmpty() bool { return s.Male == "" && s.Name == "" } type complexSt struct { A aStruct S []string IntValue int } func (c *complexSt) IsEmpty() bool { return c.A.IsEmpty() && len(c.S) == 0 && c.IntValue == 0 }
このとき、もし新しい構造体を追加してそれが空かどうかを判断する必要がある場合、対応するメソッドを実装して各フィールドをチェックする必要があります。
リフレクションを使った実装
リフレクションを使って実装する場合、Golang Empty Struct Judgment
を参照できます。このとき、対応する構造体を渡すだけで、対応するデータが空かどうかを取得でき、繰り返し実装する必要はありません。
パフォーマンス比較
func BenchmarkReflectIsStructEmpty(b *testing.B) { s := complexSt{ A: aStruct{}, S: make([]string, 0), IntValue: 0, } for i := 0; i < b.N; i++ { IsStructEmpty(s) } } func BenchmarkNormalIsStructEmpty(b *testing.B) { s := complexSt{ A: aStruct{}, S: make([]string, 0), IntValue: 0, } for i := 0; i < b.N; i++ { s.IsEmpty() } }
パフォーマンステストの実行
# -benchmem to view the number of memory allocations per operation # -benchtime=3s to specify the execution time as 3s. Generally, the results obtained in 1s, 3s, and 5s are similar. If the performance is poor, the longer the execution time, the more accurate the average performance value. # -count=3 to specify the number of executions. Multiple executions can ensure accuracy. # -cpu n to specify the number of CPU cores. Generally, increasing the number of CPU cores will improve performance, but it is not a positive - correlation relationship. Because context switching will have an impact when there are more cores, it depends on whether it is an IO - intensive or CPU - intensive application. Comparison can be made in multi - goroutine tests. go test -bench="." -benchmem -cpuprofile=cpu_profile.out -memprofile=mem_profile.out -benchtime=3s -count=3.
実行結果
BenchmarkReflectIsStructEmpty-16 8127797 493 ns/op 112 B/op 7 allocs/op BenchmarkReflectIsStructEmpty-16 6139068 540 ns/op 112 B/op 7 allocs/op BenchmarkReflectIsStructEmpty-16 7282296 465 ns/op 112 B/op 7 allocs/op BenchmarkNormalIsStructEmpty-16 1000000000 0.272 ns/op 0 B/op 0 allocs/op BenchmarkNormalIsStructEmpty-16 1000000000 0.285 ns/op 0 B/op 0 allocs/op BenchmarkNormalIsStructEmpty-16 1000000000 0.260 ns/op 0 B/op 0 allocs/op
結果分析
結果フィールドの意味:
結果項目 | 意味 |
---|---|
BenchmarkReflectIsStructEmpty - 16 | BenchmarkReflectIsStructEmptyはテスト関数の名前で、- 16はGOMAXPROCS(スレッド数)の値が16であることを示します |
2899022 | 合計2899022回の実行が行われました |
401 ns/op | 1回の操作あたり平均401ナノ秒を費やしたことを示します |
112 B/op | 1回の操作あたり112バイトのメモリが割り当てられたことを示します |
7 allocs/op | メモリが7回割り当てられたことを示します |
リフレクションによって判断される各操作の時間消費量は、直接判断の約1000倍であり、7回の追加のメモリ割り当てももたらし、毎回112バイト増加します。全体として、パフォーマンスは直接操作と比較して大幅に低下します。
リフレクションを使って同じ名前の構造体フィールドをコピーする
リフレクションを使わない実装
実際のビジネスインターフェースでは、DTO
とVO
の間でデータを変換する必要がよくあり、ほとんどの場合、同じ名前のフィールドのコピーです。このとき、リフレクションを使わない場合、各フィールドをコピーする必要があり、新しい構造体をコピーする必要がある場合、以下のようにnew
メソッドの記述を繰り返す必要があり、多くの繰り返し作業が発生します。
type aStruct struct { Name string Male string } type aStructCopy struct { Name string Male string } func newAStructCopyFromAStruct(a *aStruct) *aStructCopy { return &aStructCopy{ Name: a.Name, Male: a.Male, } }
リフレクションを使った実装
リフレクションを使って構造体をコピーする場合、コピーする必要がある新しい構造体がある場合、同じ名前のフィールドをコピーするために構造体ポインタを渡すだけで済みます。実装は以下のようになります。
func CopyIntersectionStruct(src, dst interface{}) { sElement := reflect.ValueOf(src).Elem() dElement := reflect.ValueOf(dst).Elem() for i := 0; i < dElement.NumField(); i++ { dField := dElement.Type().Field(i) sValue := sElement.FieldByName(dField.Name) if!sValue.IsValid() { continue } value := dElement.Field(i) value.Set(sValue) } }
パフォーマンス比較
func BenchmarkCopyIntersectionStruct(b *testing.B) { a := &aStruct{ Name: "test", Male: "test", } for i := 0; i < b.N; i++ { var ac aStructCopy CopyIntersectionStruct(a, &ac) } } func BenchmarkNormalCopyIntersectionStruct(b *testing.B) { a := &aStruct{ Name: "test", Male: "test", } for i := 0; i < b.N; i++ { newAStructCopyFromAStruct(a) } }
パフォーマンステストの実行
go test -bench="." -benchmem -cpuprofile=cpu_profile.out -memprofile=mem_profile.out -benchtime=3s -count=3.
実行結果
BenchmarkCopyIntersectionStruct-16 10789202 352 ns/op 64 B/op 5 allocs/op BenchmarkCopyIntersectionStruct-16 10877558 304 ns/op 64 B/op 5 allocs/op BenchmarkCopyIntersectionStruct-16 10167404 322 ns/op 64 B/op 5 allocs/op BenchmarkNormalCopyIntersectionStruct-16 1000000000 0.277 ns/op 0 B/op 0 allocs/op BenchmarkNormalCopyIntersectionStruct-16 1000000000 0.270 ns/op 0 B/op 0 allocs/op BenchmarkNormalCopyIntersectionStruct-16 1000000000 0.259 ns/op 0 B/op 0 allocs/op
上記の一番目の実行結果と同様に、リフレクションの時間消費量は依然としてリフレクションを使用しない場合の1000倍であり、メモリ割り当ても毎回64バイト増加します。実際のビジネスシナリオでは、複数のリフレクションが組み合わされる場合があります。実際のパフォーマンスをテストする必要がある場合は、独自のBenchmarkTestを作成できます。フレームグラフを比較すると、実行時間の割合をより明確に示すことができます。
結論
ビジネスインターフェースでは、インターフェースの応答が10msであると仮定し、リフレクションメソッドの平均操作が400ナノ秒であり、約64〜112バイトの追加のメモリ割り当てが発生します。
1ms [millisecond] = 1000μs [microsecond]=1000 * 1000ns [nanosecond] 1MB = 1024KB = 1024 * 1024 B
インターフェースがリンク内で1000回のリフレクション操作を実行する場合、1回の操作でインターフェースの遅延が約0.4ms増加します。一般的に、単一のリクエストのミドルウェアおよびビジネス操作の数はこの数に達することはめったにないため、応答時間への影響は基本的に無視できます。実際のビジネスでは、メモリコピーとネットワークIOでより多くの損失が発生します。
ただし、リフレクションにはコーディングにおける実際の問題もあります。通常のビジネスコードよりも保守および理解が困難です。したがって、過度な使用を避けるために、使用する際には慎重に検討する必要があり、コードの複雑さが絶えず増大します。
Leapcell:Golangアプリホスティングに最適なサーバーレスプラットフォーム
最後に、Golangサービスをデプロイするための最適なプラットフォームをお勧めします:Leapcell
1. マルチ言語サポート
- JavaScript、Python、Go、またはRustで開発します。
2. 無制限のプロジェクトを無料でデプロイ
- 使用量に対してのみ料金を支払います—リクエストも料金もかかりません。
3. 無敵のコスト効率
- アイドル料金なしで従量課金制。
- 例:25ドルで、平均応答時間60msで694万リクエストをサポートします。
4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI / CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムのメトリックとロギング。
5. 簡単なスケーラビリティとハイパフォーマンス
- 高い同時実行性を簡単に処理するための自動スケーリング。
- 運用上のオーバーヘッドはゼロ—構築に集中してください。
Leapcell Twitter:https://x.com/LeapcellHQ