任意の型を引数に取り、そのフィールドの値を集計して返す
業務で利用しようとし、実装したのですがPerformance Issueにより不使用にしたので供養がてらブログネタとします。
type Fruits struct { Apple float32 Banana float32 Orange float32 Strawberry float32 Raspberry float32 Kiwi float32 Coconut float32 Papaya float32 Grapefruit float32 Avocado float32 }
のような構造体があるとします。float32には価格が格納されています。また、この構造体は追加・削除がそこそこ発生します。 この時Fruits全体の合計値を知りたいと思った時、愚直に足し算を実装しても良いのですが、構造体の追加・削除が発生した時に実装の修正が必要になります。
これは少々煩雑なので、Fieldの数に依存せずに合計を計算出来ないかと考え、Reflectionを利用してFieldのValueを取得する関数を作ってみました。
(結構Copilotが作ってくれた)
func FruitsAllWithType(s interface{}) []float32 { v := reflect.ValueOf(s) typeOfS := v.Type() scores := make([]float32, v.NumField()) for i := 0; i < v.NumField(); i++ { fieldName := typeOfS.Field(i).Name fieldValue := v.FieldByName(fieldName).Interface() if value, ok := fieldValue.(float32); ok { scores = append(scores, value) } } return scores }
任意のstruct sをfor文でひたすら回して、逐次Fileld名を取得し、そのField名でValueをInterfaceとして取り出し、型がfloat32であればsliceに追加するという実装です。 以下で試すことができます go.dev
この実装は前述の通りPerformance Issueがあるため不使用としました(まあそもそも map[string]float32
のほうが利便性含め良くないかってこともあったんですが)
先ほどの関数をgo標準のtesting packageにあるbenckmarkを利用して、計算時間を計測しました。 10種類のFields及びmap[string]float32の足し合わせを100万回ループさせた時の測定結果です
goos: darwin goarch: arm64 BenchmarkCalCAllFruitPrice1-12 1000000000 1.056 ns/op BenchmarkCalCAllFruitPrice2-12 1000000000 0.08979 ns/op PASS ok 70.150s
- BenchmarkCalCAllFruitPrice1-12: Fieldを集計したもの
- BenchmarkCalCAllFruitPrice2-12: mapを集計したもの
1ベンチマークループで10.6倍程度の差が付きました。 動的に処理することや毎回の型検査が入ってくるのでパフォーマンスに大きな影響が出ていることが分かります。
オンラインのワークロードではない、速度を重要視しない内容や、非構造なデータを扱う時にReflectionは力を発揮するので、用法用量を守って利用しましょう。