This is an automated email from the ASF dual-hosted git repository.
Alanxtl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 0139a9bad perf(loadbalance): reduce temporary selection allocations
(#3415)
0139a9bad is described below
commit 0139a9bad92b941b93fed8bce08e2e7389a6f9e7
Author: ヴァニラシ <[email protected]>
AuthorDate: Sun Jun 14 15:20:23 2026 +0800
perf(loadbalance): reduce temporary selection allocations (#3415)
---
cluster/loadbalance/aliasmethod/alias_method.go | 25 +++++++++--
cluster/loadbalance/leastactive/loadbalance.go | 28 +++++++++----
cluster/loadbalance/loadbalance_benchmarks_test.go | 48 ++++++++++++++++++++++
cluster/loadbalance/random/loadbalance.go | 16 +++++++-
4 files changed, 105 insertions(+), 12 deletions(-)
diff --git a/cluster/loadbalance/aliasmethod/alias_method.go
b/cluster/loadbalance/aliasmethod/alias_method.go
index cba727af1..f6335e671 100644
--- a/cluster/loadbalance/aliasmethod/alias_method.go
+++ b/cluster/loadbalance/aliasmethod/alias_method.go
@@ -28,6 +28,8 @@ import (
"dubbo.apache.org/dubbo-go/v3/protocol/base"
)
+const smallInvokerThreshold = 32
+
type aliasMethodPicker struct {
invokers []base.Invoker // Instance
@@ -47,15 +49,30 @@ func NewAliasMethodPicker(invokers []base.Invoker,
invocation base.Invocation) *
// Alias Method: https://en.wikipedia.org/wiki/Alias_method
func (am *aliasMethodPicker) init(invocation base.Invocation) {
n := len(am.invokers)
- weights := make([]int64, n)
am.alias = make([]int, n)
am.prob = make([]float64, n)
totalWeight := int64(0)
- scaledProb := make([]float64, n)
- small := make([]int, 0, n)
- large := make([]int, 0, n)
+ var (
+ weightStack [smallInvokerThreshold]int64
+ scaledProbStack [smallInvokerThreshold]float64
+ smallStack [smallInvokerThreshold]int
+ largeStack [smallInvokerThreshold]int
+ )
+ weights := weightStack[:]
+ scaledProb := scaledProbStack[:]
+ small := smallStack[:0]
+ large := largeStack[:0]
+ if n > smallInvokerThreshold {
+ weights = make([]int64, n)
+ scaledProb = make([]float64, n)
+ small = make([]int, 0, n)
+ large = make([]int, 0, n)
+ } else {
+ weights = weights[:n]
+ scaledProb = scaledProb[:n]
+ }
now := time.Now().Unix()
for i, invoker := range am.invokers {
diff --git a/cluster/loadbalance/leastactive/loadbalance.go
b/cluster/loadbalance/leastactive/loadbalance.go
index 27c8f7f52..4dd37316c 100644
--- a/cluster/loadbalance/leastactive/loadbalance.go
+++ b/cluster/loadbalance/leastactive/loadbalance.go
@@ -32,6 +32,9 @@ import (
const (
// Key is used to set the load balance extension
Key = "leastactive"
+
+ minStackInvokerCount = 8
+ maxStackInvokerCount = 32
)
func init() {
@@ -58,14 +61,25 @@ func (lb *leastActiveLoadBalance) Select(invokers
[]base.Invoker, invocation bas
}
var (
- leastActive int32 = -1 // The least active
value of all invokers
- totalWeight int64 // The number of
invokers having the same least active value (LEAST_ACTIVE)
- firstWeight int64 // Initial value, used
for comparison
- leastCount int // The number of
invokers having the same least active value (LEAST_ACTIVE)
- leastIndexes = make([]int, count) // The index of
invokers having the same least active value (LEAST_ACTIVE)
- sameWeight = true // Every invoker has
the same weight value?
- weights = make([]int64, count) // The weight of every
invokers
+ leastActive int32 = -1 // The least active value of all
invokers
+ totalWeight int64 // Sum of weights of invokers having
the same least active value (LEAST_ACTIVE)
+ firstWeight int64 // Initial value, used for comparison
+ leastCount int // The number of invokers having the
same least active value (LEAST_ACTIVE)
+ leastIndexes []int // The index of invokers having the
same least active value (LEAST_ACTIVE)
+ sameWeight = true // Every invoker has the same weight
value?
+ weights []int64 // The weight of every invokers
+ )
+ var (
+ leastIndexStack [maxStackInvokerCount]int
+ weightStack [maxStackInvokerCount]int64
)
+ if count >= minStackInvokerCount && count <= maxStackInvokerCount {
+ leastIndexes = leastIndexStack[:count]
+ weights = weightStack[:count]
+ } else {
+ leastIndexes = make([]int, count)
+ weights = make([]int64, count)
+ }
now := time.Now().Unix()
for i := 0; i < count; i++ {
diff --git a/cluster/loadbalance/loadbalance_benchmarks_test.go
b/cluster/loadbalance/loadbalance_benchmarks_test.go
index f8a483895..612d31ea0 100644
--- a/cluster/loadbalance/loadbalance_benchmarks_test.go
+++ b/cluster/loadbalance/loadbalance_benchmarks_test.go
@@ -48,6 +48,19 @@ func Generate() []base.Invoker {
return invokers
}
+func generateInvokers(count int, weighted bool) []base.Invoker {
+ invokers := make([]base.Invoker, 0, count)
+ for i := 1; i <= count; i++ {
+ rawURL :=
fmt.Sprintf("dubbo://192.168.1.%v:20000/org.apache.demo.HelloService", i)
+ if weighted {
+ rawURL = fmt.Sprintf("%s?weight=%d", rawURL, i)
+ }
+ url, _ := common.NewURL(rawURL)
+ invokers = append(invokers, base.NewBaseInvoker(url))
+ }
+ return invokers
+}
+
func Benchloadbalance(b *testing.B, lb loadbalance.LoadBalance) {
b.Helper()
invokers := Generate()
@@ -106,3 +119,38 @@ func BenchmarkGetWeightAt(b *testing.B) {
loadbalance.GetWeightAt(invokers[i%len(invokers)], inv, now)
}
}
+
+func benchmarkLoadBalanceSmallMedium(b *testing.B, lb loadbalance.LoadBalance)
{
+ b.Helper()
+ for _, count := range []int{2, 4, 8, 16, 32, 33} {
+ for _, weighted := range []bool{false, true} {
+ name := fmt.Sprintf("invokers=%d", count)
+ if weighted {
+ name += "/weighted"
+ } else {
+ name += "/uniform"
+ }
+ b.Run(name, func(b *testing.B) {
+ invokers := generateInvokers(count, weighted)
+ rpcInvocation := &invocation.RPCInvocation{}
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ lb.Select(invokers, rpcInvocation)
+ }
+ })
+ }
+ }
+}
+
+func BenchmarkRandomLoadbalanceSmallMedium(b *testing.B) {
+ benchmarkLoadBalanceSmallMedium(b,
extension.GetLoadbalance(constant.LoadBalanceKeyRandom))
+}
+
+func BenchmarkLeastactiveLoadbalanceSmallMedium(b *testing.B) {
+ benchmarkLoadBalanceSmallMedium(b,
extension.GetLoadbalance(constant.LoadBalanceKeyLeastActive))
+}
+
+func BenchmarkAliasMethodLoadbalanceSmallMedium(b *testing.B) {
+ benchmarkLoadBalanceSmallMedium(b,
extension.GetLoadbalance(constant.LoadBalanceKeyAliasMethod))
+}
diff --git a/cluster/loadbalance/random/loadbalance.go
b/cluster/loadbalance/random/loadbalance.go
index 43ce7ee81..3c80a4781 100644
--- a/cluster/loadbalance/random/loadbalance.go
+++ b/cluster/loadbalance/random/loadbalance.go
@@ -53,7 +53,21 @@ func (lb *randomLoadBalance) Select(invokers []base.Invoker,
invocation base.Inv
// Every invoker has the same weight?
sameWeight := true
// the maxWeight of every invokers, the minWeight = 0 or the maxWeight
of the last invoker
- weights := make([]int64, length)
+ // Use stack buffers for common small invoker lists to avoid the
temporary weights slice allocation.
+ var weights []int64
+ switch {
+ case length <= 8:
+ var weightStack [8]int64
+ weights = weightStack[:length:length]
+ case length <= 16:
+ var weightStack [16]int64
+ weights = weightStack[:length:length]
+ case length <= 32:
+ var weightStack [32]int64
+ weights = weightStack[:length:length]
+ default:
+ weights = make([]int64, length)
+ }
// The sum of weights
var totalWeight int64 = 0