Skip to content

Commit 5e854d9

Browse files
committed
make topologyTerm a slice
Compared with map, slice is a lot faster. And we can avoid allocation in the compare() func, which is a very hot path.
1 parent 872c65f commit 5e854d9

File tree

2 files changed

+272
-32
lines changed

2 files changed

+272
-32
lines changed

pkg/controller/topology.go

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import (
2020
"fmt"
2121
"hash/fnv"
2222
"math/rand"
23-
"sort"
23+
"slices"
2424
"strconv"
2525
"strings"
2626

@@ -39,8 +39,14 @@ import (
3939
"k8s.io/klog/v2"
4040
)
4141

42+
type topologySegment struct {
43+
Key, Value string
44+
}
45+
4246
// topologyTerm represents a single term where its topology key value pairs are AND'd together.
43-
type topologyTerm map[string]string
47+
//
48+
// Be sure to sort after construction for compare() and subset() to work properly.
49+
type topologyTerm []topologySegment
4450

4551
func GenerateVolumeNodeAffinity(accessibleTopology []*csi.Topology) *v1.VolumeNodeAffinity {
4652
if len(accessibleTopology) == 0 {
@@ -417,12 +423,12 @@ func flatten(allowedTopologies []v1.TopologySelectorTerm) []topologyTerm {
417423

418424
if len(oldTerms) == 0 {
419425
// No previous terms to distribute over. Simply append the new term.
420-
newTerms = append(newTerms, topologyTerm{selectorExpression.Key: v})
426+
newTerms = append(newTerms, topologyTerm{{selectorExpression.Key, v}})
421427
} else {
422428
for _, oldTerm := range oldTerms {
423429
// "Distribute" by adding an entry to the term
424-
newTerm := oldTerm.clone()
425-
newTerm[selectorExpression.Key] = v
430+
newTerm := slices.Clone(oldTerm)
431+
newTerm = append(newTerm, topologySegment{selectorExpression.Key, v})
426432
newTerms = append(newTerms, newTerm)
427433
}
428434
}
@@ -435,6 +441,9 @@ func flatten(allowedTopologies []v1.TopologySelectorTerm) []topologyTerm {
435441
finalTerms = append(finalTerms, oldTerms...)
436442
}
437443

444+
for _, term := range finalTerms {
445+
term.sort()
446+
}
438447
return finalTerms
439448
}
440449

@@ -456,9 +465,7 @@ func deduplicate(terms []topologyTerm) []topologyTerm {
456465
// either the primary term (if specified) or term at shiftIndex is the first in the list.
457466
func sortAndShift(terms []topologyTerm, primary topologyTerm, shiftIndex uint32) []topologyTerm {
458467
var preferredTerms []topologyTerm
459-
sort.Slice(terms, func(i, j int) bool {
460-
return terms[i].less(terms[j])
461-
})
468+
slices.SortFunc(terms, topologyTerm.compare)
462469
if primary == nil {
463470
preferredTerms = append(terms[shiftIndex:], terms[:shiftIndex]...)
464471
} else {
@@ -482,14 +489,15 @@ func getTopologyKeys(csiNode *storagev1.CSINode, driverName string) []string {
482489
}
483490

484491
func getTopologyFromNode(node *v1.Node, topologyKeys []string) (term topologyTerm, isMissingKey bool) {
485-
term = make(topologyTerm)
492+
term = make(topologyTerm, 0, len(topologyKeys))
486493
for _, key := range topologyKeys {
487494
v, ok := node.Labels[key]
488495
if !ok {
489496
return nil, true
490497
}
491-
term[key] = v
498+
term = append(term, topologySegment{key, v})
492499
}
500+
term.sort()
493501
return term, false
494502
}
495503

@@ -514,12 +522,15 @@ func buildTopologyKeySelector(topologyKeys []string) (labels.Selector, error) {
514522
return selector, nil
515523
}
516524

517-
func (t topologyTerm) clone() topologyTerm {
518-
ret := make(topologyTerm)
519-
for k, v := range t {
520-
ret[k] = v
521-
}
522-
return ret
525+
func (t topologyTerm) sort() {
526+
slices.SortFunc(t, func(a, b topologySegment) int {
527+
r := strings.Compare(a.Key, b.Key)
528+
if r != 0 {
529+
return r
530+
}
531+
// Should not happen currently. We may support multi-value in the future?
532+
return strings.Compare(a.Value, b.Value)
533+
})
523534
}
524535

525536
// "<k1>#<v1>,<k2>#<v2>,..."
@@ -533,37 +544,61 @@ func (t topologyTerm) clone() topologyTerm {
533544
// Note that both '#' and ',' are less than '/', '-', '_', '.', [A-Z0-9a-z]
534545
func (t topologyTerm) hash() string {
535546
var segments []string
536-
for k, v := range t {
537-
segments = append(segments, k+"#"+v)
547+
for _, k := range t {
548+
segments = append(segments, k.Key+"#"+k.Value)
538549
}
539550

540-
sort.Strings(segments)
541551
return strings.Join(segments, ",")
542552
}
543553

544-
func (t topologyTerm) less(other topologyTerm) bool {
545-
return t.hash() < other.hash()
554+
func (t topologyTerm) compare(other topologyTerm) int {
555+
if len(t) != len(other) {
556+
return len(t) - len(other)
557+
}
558+
for i, k1 := range t {
559+
k2 := other[i]
560+
r := strings.Compare(k1.Key, k2.Key)
561+
if r != 0 {
562+
return r
563+
}
564+
r = strings.Compare(k1.Value, k2.Value)
565+
if r != 0 {
566+
return r
567+
}
568+
}
569+
return 0
546570
}
547571

548572
func (t topologyTerm) subset(other topologyTerm) bool {
549-
for key, tv := range t {
550-
v, ok := other[key]
551-
if !ok || v != tv {
573+
if len(t) == 0 {
574+
return true
575+
}
576+
j := 0
577+
for _, k2 := range other {
578+
k1 := t[j]
579+
if k1.Key != k2.Key {
580+
continue
581+
}
582+
if k1.Value != k2.Value {
552583
return false
553584
}
585+
j++
586+
if j == len(t) {
587+
// All segments in t have been checked and is present in other.
588+
return true
589+
}
554590
}
555-
556-
return true
557-
}
558-
559-
func (t topologyTerm) equal(other topologyTerm) bool {
560-
return t.hash() == other.hash()
591+
return false
561592
}
562593

563594
func toCSITopology(terms []topologyTerm) []*csi.Topology {
564-
var out []*csi.Topology
595+
out := make([]*csi.Topology, 0, len(terms))
565596
for _, term := range terms {
566-
out = append(out, &csi.Topology{Segments: term})
597+
segs := make(map[string]string, len(term))
598+
for _, k := range term {
599+
segs[k.Key] = k.Value
600+
}
601+
out = append(out, &csi.Topology{Segments: segs})
567602
}
568603
return out
569604
}

0 commit comments

Comments
 (0)