From 18944885287b6bd096a23031478fe195bf7fa888 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 19 Sep 2025 14:50:11 -0700 Subject: [PATCH] perf: remove reflect from BackendRefContext * similar to https://github.com/envoyproxy/gateway/pull/6820 ``` BenchmarkBackendRefContext_Old_HTTPBackendRef-12 5002508 239.5 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_HTTPBackendRef-12 4764154 264.1 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_HTTPBackendRef-12 4866708 238.6 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_HTTPBackendRef-12 5032014 243.8 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_HTTPBackendRef-12 4935594 259.5 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_GRPCBackendRef-12 4833061 245.3 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_GRPCBackendRef-12 4970496 252.1 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_GRPCBackendRef-12 4517140 253.2 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_GRPCBackendRef-12 4556660 273.4 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_Old_GRPCBackendRef-12 5003169 244.2 ns/op 152 B/op 3 allocs/op BenchmarkBackendRefContext_New_HTTPBackendRef-12 1000000000 0.2556 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_HTTPBackendRef-12 1000000000 0.2495 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_HTTPBackendRef-12 1000000000 0.2499 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_HTTPBackendRef-12 1000000000 0.2576 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_HTTPBackendRef-12 1000000000 0.2556 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_GRPCBackendRef-12 1000000000 0.2505 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_GRPCBackendRef-12 1000000000 0.2504 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_GRPCBackendRef-12 1000000000 0.2591 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_GRPCBackendRef-12 1000000000 0.2502 ns/op 0 B/op 0 allocs/op BenchmarkBackendRefContext_New_GRPCBackendRef-12 1000000000 0.2483 ns/op 0 B/op 0 allocs/op ``` Signed-off-by: Arko Dasgupta --- internal/gatewayapi/contexts.go | 44 ++++++++++++++++---------- internal/gatewayapi/filters.go | 22 ++++--------- internal/gatewayapi/listener.go | 3 +- internal/gatewayapi/resource/load.go | 8 ++--- internal/gatewayapi/route.go | 46 ++++++++++++++++++---------- internal/gatewayapi/validate.go | 7 +++-- 6 files changed, 74 insertions(+), 56 deletions(-) diff --git a/internal/gatewayapi/contexts.go b/internal/gatewayapi/contexts.go index f9e8fb7d96..8841e48a73 100644 --- a/internal/gatewayapi/contexts.go +++ b/internal/gatewayapi/contexts.go @@ -585,25 +585,35 @@ func (r *RouteParentContext) HasCondition(route RouteContext, condType gwapiv1.R return false } -// BackendRefContext represents a generic BackendRef object (HTTPBackendRef, GRPCBackendRef or BackendRef itself) -type BackendRefContext any +// BackendRefContext represents a generic BackendRef object +type BackendRefContext interface { + GetBackendRef() *gwapiv1.BackendRef + GetFilters() any +} -func GetBackendRef(b BackendRefContext) *gwapiv1.BackendRef { - rv := reflect.ValueOf(b) - br := rv.FieldByName("BackendRef") - if br.IsValid() { - backendRef := br.Interface().(gwapiv1.BackendRef) - return &backendRef - } +// BackendRefWithFilters wraps backend refs that have filters (HTTPBackendRef, GRPCBackendRef) +type BackendRefWithFilters struct { + BackendRef *gwapiv1.BackendRef + Filters any // []gwapiv1.HTTPRouteFilter or []gwapiv1.GRPCRouteFilter +} - backendRef := b.(gwapiv1.BackendRef) - return &backendRef +func (b BackendRefWithFilters) GetBackendRef() *gwapiv1.BackendRef { + return b.BackendRef } -func GetFilters(b BackendRefContext) any { - filters := reflect.ValueOf(b).FieldByName("Filters") - if !filters.IsValid() { - return nil - } - return filters.Interface() +func (b BackendRefWithFilters) GetFilters() any { + return b.Filters +} + +// DirectBackendRef wraps a BackendRef directly (used by TLS/TCP/UDP routes) +type DirectBackendRef struct { + BackendRef *gwapiv1.BackendRef +} + +func (d DirectBackendRef) GetBackendRef() *gwapiv1.BackendRef { + return d.BackendRef +} + +func (d DirectBackendRef) GetFilters() any { + return nil } diff --git a/internal/gatewayapi/filters.go b/internal/gatewayapi/filters.go index e6e3fe5e7d..946225048c 100644 --- a/internal/gatewayapi/filters.go +++ b/internal/gatewayapi/filters.go @@ -969,22 +969,12 @@ func (t *Translator) processRequestMirrorFilter( weight := int32(1) mirrorBackend := mirrorFilter.BackendRef - // Create the appropriate BackendRef type based on the route type - var mirrorBackendRef BackendRefContext - if routeType == resource.KindGRPCRoute { - mirrorBackendRef = gwapiv1.GRPCBackendRef{ - BackendRef: gwapiv1.BackendRef{ - BackendObjectReference: mirrorBackend, - Weight: &weight, - }, - } - } else { - mirrorBackendRef = gwapiv1.HTTPBackendRef{ - BackendRef: gwapiv1.BackendRef{ - BackendObjectReference: mirrorBackend, - Weight: &weight, - }, - } + // Create a DirectBackendRef for the mirror backend (no filters needed) + mirrorBackendRef := DirectBackendRef{ + BackendRef: &gwapiv1.BackendRef{ + BackendObjectReference: mirrorBackend, + Weight: &weight, + }, } // This sets the status on the Route, should the usage be changed so that the status message reflects that the backendRef is from the filter? diff --git a/internal/gatewayapi/listener.go b/internal/gatewayapi/listener.go index 74fac24685..5938ae5946 100644 --- a/internal/gatewayapi/listener.go +++ b/internal/gatewayapi/listener.go @@ -819,7 +819,8 @@ func (t *Translator) processBackendRefs(name string, backendCluster egv1a1.Backe return nil, nil, err } result := make([]*ir.DestinationSetting, 0, len(backendCluster.BackendRefs)) - for _, ref := range backendCluster.BackendRefs { + for i := range backendCluster.BackendRefs { + ref := &backendCluster.BackendRefs[i] ns := NamespaceDerefOr(ref.Namespace, namespace) kind := KindDerefOr(ref.Kind, resource.KindService) switch kind { diff --git a/internal/gatewayapi/resource/load.go b/internal/gatewayapi/resource/load.go index 6c327f16ef..540cf1497a 100644 --- a/internal/gatewayapi/resource/load.go +++ b/internal/gatewayapi/resource/load.go @@ -544,15 +544,15 @@ func addMissingServices(requiredServices map[string]*corev1.Service, obj interfa case *gwapiv1.HTTPRoute: objNamespace = route.Namespace for _, rule := range route.Spec.Rules { - for _, httpBakcendRef := range rule.BackendRefs { - refs = append(refs, httpBakcendRef.BackendRef) + for i := range rule.BackendRefs { + refs = append(refs, rule.BackendRefs[i].BackendRef) } } case *gwapiv1.GRPCRoute: objNamespace = route.Namespace for _, rule := range route.Spec.Rules { - for _, gRPCBakcendRef := range rule.BackendRefs { - refs = append(refs, gRPCBakcendRef.BackendRef) + for i := range rule.BackendRefs { + refs = append(refs, rule.BackendRefs[i].BackendRef) } } case *gwapiv1a2.TLSRoute: diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index 4addd4c86b..3bc741174a 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -218,9 +218,13 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe backendRefNames := make([]string, len(rule.BackendRefs)) backendCustomRefs := []*ir.UnstructuredRef{} // process each backendRef, and calculate the destination settings for this rule - for i, backendRef := range rule.BackendRefs { + for i := range rule.BackendRefs { settingName := irDestinationSettingName(destName, i) - ds, unstructuredRef, err := t.processDestination(settingName, backendRef, parentRef, httpRoute, resources) + backendRefCtx := BackendRefWithFilters{ + BackendRef: &rule.BackendRefs[i].BackendRef, + Filters: rule.BackendRefs[i].Filters, + } + ds, unstructuredRef, err := t.processDestination(settingName, backendRefCtx, parentRef, httpRoute, resources) if err != nil { errs.Add(status.NewRouteStatusError( fmt.Errorf("failed to process route rule %d backendRef %d: %w", ruleIdx, i, err), @@ -242,8 +246,8 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe if ds.IsDynamicResolver { hasDynamicResolver = true } - backendNamespace := NamespaceDerefOr(backendRef.Namespace, httpRoute.GetNamespace()) - backendRefNames[i] = fmt.Sprintf("%s/%s", backendNamespace, backendRef.Name) + backendNamespace := NamespaceDerefOr(rule.BackendRefs[i].Namespace, httpRoute.GetNamespace()) + backendRefNames[i] = fmt.Sprintf("%s/%s", backendNamespace, rule.BackendRefs[i].Name) } // process each ir route @@ -664,9 +668,13 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe failedProcessDestination := false backendRefNames := make([]string, len(rule.BackendRefs)) - for i, backendRef := range rule.BackendRefs { + for i := range rule.BackendRefs { settingName := irDestinationSettingName(destName, i) - ds, _, err := t.processDestination(settingName, backendRef, parentRef, grpcRoute, resources) + backendRefCtx := BackendRefWithFilters{ + BackendRef: &rule.BackendRefs[i].BackendRef, + Filters: rule.BackendRefs[i].Filters, + } + ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, grpcRoute, resources) if err != nil { errs.Add(status.NewRouteStatusError( fmt.Errorf("failed to process route rule %d backendRef %d: %w", ruleIdx, i, err), @@ -680,8 +688,8 @@ func (t *Translator) processGRPCRouteRules(grpcRoute *GRPCRouteContext, parentRe continue } allDs = append(allDs, ds) - backendNamespace := NamespaceDerefOr(backendRef.Namespace, grpcRoute.GetNamespace()) - backendRefNames[i] = fmt.Sprintf("%s/%s", backendNamespace, backendRef.Name) + backendNamespace := NamespaceDerefOr(rule.BackendRefs[i].Namespace, grpcRoute.GetNamespace()) + backendRefNames[i] = fmt.Sprintf("%s/%s", backendNamespace, rule.BackendRefs[i].Name) } // process each ir route @@ -955,9 +963,10 @@ func (t *Translator) processTLSRouteParentRefs(tlsRoute *TLSRouteContext, resour // compute backends for _, rule := range tlsRoute.Spec.Rules { - for i, backendRef := range rule.BackendRefs { + for i := range rule.BackendRefs { settingName := irDestinationSettingName(destName, i) - ds, _, err := t.processDestination(settingName, backendRef, parentRef, tlsRoute, resources) + backendRefCtx := DirectBackendRef{BackendRef: &rule.BackendRefs[i]} + ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, tlsRoute, resources) if err != nil { resolveErrs.Add(err) continue @@ -1110,9 +1119,10 @@ func (t *Translator) processUDPRouteParentRefs(udpRoute *UDPRouteContext, resour destName = irRouteDestinationName(udpRoute, -1 /*rule index*/) ) - for i, backendRef := range udpRoute.Spec.Rules[0].BackendRefs { + for i := range udpRoute.Spec.Rules[0].BackendRefs { settingName := irDestinationSettingName(destName, i) - ds, _, err := t.processDestination(settingName, backendRef, parentRef, udpRoute, resources) + backendRefCtx := DirectBackendRef{BackendRef: &udpRoute.Spec.Rules[0].BackendRefs[i]} + ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, udpRoute, resources) if err != nil { resolveErrs.Add(err) continue @@ -1259,9 +1269,10 @@ func (t *Translator) processTCPRouteParentRefs(tcpRoute *TCPRouteContext, resour destName = irRouteDestinationName(tcpRoute, -1 /*rule index*/) ) - for i, backendRef := range tcpRoute.Spec.Rules[0].BackendRefs { + for i := range tcpRoute.Spec.Rules[0].BackendRefs { settingName := irDestinationSettingName(destName, i) - ds, _, err := t.processDestination(settingName, backendRef, parentRef, tcpRoute, resources) + backendRefCtx := DirectBackendRef{BackendRef: &tcpRoute.Spec.Rules[0].BackendRefs[i]} + ds, _, err := t.processDestination(settingName, backendRefCtx, parentRef, tcpRoute, resources) // skip adding the route and provide the reason via route status. if err != nil { resolveErrs.Add(err) @@ -1375,7 +1386,7 @@ func (t *Translator) processDestination(name string, backendRefContext BackendRe ) (ds *ir.DestinationSetting, unstructuredRef *ir.UnstructuredRef, err status.Error) { routeType := route.GetRouteType() weight := uint32(1) - backendRef := GetBackendRef(backendRefContext) + backendRef := backendRefContext.GetBackendRef() if backendRef.Weight != nil { weight = uint32(*backendRef.Weight) } @@ -1606,7 +1617,10 @@ func (t *Translator) processServiceDestinationSetting( } func getBackendFilters(routeType gwapiv1.Kind, backendRefContext BackendRefContext) (backendFilters any) { - filters := GetFilters(backendRefContext) + filters := backendRefContext.GetFilters() + if filters == nil { + return nil + } switch routeType { case resource.KindHTTPRoute: if len(filters.([]gwapiv1.HTTPRouteFilter)) > 0 { diff --git a/internal/gatewayapi/validate.go b/internal/gatewayapi/validate.go index 8a4edaa460..e4093ae94e 100644 --- a/internal/gatewayapi/validate.go +++ b/internal/gatewayapi/validate.go @@ -29,7 +29,7 @@ import ( func (t *Translator) validateBackendRef(backendRefContext BackendRefContext, route RouteContext, resources *resource.Resources, backendNamespace string, routeKind gwapiv1.Kind, ) status.Error { - backendRef := GetBackendRef(backendRefContext) + backendRef := backendRefContext.GetBackendRef() if err := t.validateBackendRefFilters(backendRefContext, routeKind); err != nil { return err @@ -91,7 +91,10 @@ func (t *Translator) validateBackendRefKind(backendRef *gwapiv1a2.BackendRef) st } func (t *Translator) validateBackendRefFilters(backendRef BackendRefContext, routeKind gwapiv1.Kind) status.Error { - filters := GetFilters(backendRef) + filters := backendRef.GetFilters() + if filters == nil { + return nil + } var unsupportedFilters bool switch routeKind {