mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-12 05:21:58 +00:00
Optimize turning a Set into a Selector
Fixes 112647 Benchmark: ``` $ benchstat /tmp/{old,new} name old time/op new time/op delta SelectorFromValidatedSet-48 397ns ± 1% 123ns ± 0% -68.87% (p=0.008 n=5+5) name old alloc/op new alloc/op delta SelectorFromValidatedSet-48 192B ± 0% 0B -100.00% (p=0.008 n=5+5) name old allocs/op new allocs/op delta SelectorFromValidatedSet-48 5.00 ± 0% 0.00 -100.00% (p=0.008 n=5+5) ```
This commit is contained in:
parent
299e65cdc5
commit
3f2c0e1740
@ -505,7 +505,7 @@ func TestPreFilterState(t *testing.T) {
|
|||||||
{
|
{
|
||||||
MaxSkew: 3,
|
MaxSkew: 3,
|
||||||
TopologyKey: "node",
|
TopologyKey: "node",
|
||||||
Selector: mustConvertLabelSelectorAsSelector(t, st.MakeLabelSelector().Label("foo", "bar").Obj()),
|
Selector: labels.SelectorFromValidatedSet(labels.Set{"foo": "bar"}),
|
||||||
MinDomains: 1,
|
MinDomains: 1,
|
||||||
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
|
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
|
||||||
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
|
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
|
||||||
@ -513,7 +513,7 @@ func TestPreFilterState(t *testing.T) {
|
|||||||
{
|
{
|
||||||
MaxSkew: 5,
|
MaxSkew: 5,
|
||||||
TopologyKey: "rack",
|
TopologyKey: "rack",
|
||||||
Selector: mustConvertLabelSelectorAsSelector(t, st.MakeLabelSelector().Label("foo", "bar").Obj()),
|
Selector: labels.SelectorFromValidatedSet(labels.Set{"foo": "bar"}),
|
||||||
MinDomains: 1,
|
MinDomains: 1,
|
||||||
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
|
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
|
||||||
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
|
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
|
||||||
@ -1283,7 +1283,7 @@ func TestPreFilterState(t *testing.T) {
|
|||||||
{
|
{
|
||||||
MaxSkew: 1,
|
MaxSkew: 1,
|
||||||
TopologyKey: "zone",
|
TopologyKey: "zone",
|
||||||
Selector: mustConvertLabelSelectorAsSelector(t, st.MakeLabelSelector().Label("foo", "a").Obj()),
|
Selector: labels.SelectorFromValidatedSet(labels.Set{"foo": "a"}),
|
||||||
MinDomains: 1,
|
MinDomains: 1,
|
||||||
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
|
NodeAffinityPolicy: v1.NodeInclusionPolicyHonor,
|
||||||
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
|
NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore,
|
||||||
|
@ -919,6 +919,14 @@ func SelectorFromSet(ls Set) Selector {
|
|||||||
return SelectorFromValidatedSet(ls)
|
return SelectorFromValidatedSet(ls)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Create constants for hot-path selectors
|
||||||
|
value0Path, keyPath = func() (*field.Path, *field.Path) {
|
||||||
|
path := field.ToPath()
|
||||||
|
return path.Child("values").Index(0), path.Child("key")
|
||||||
|
}()
|
||||||
|
)
|
||||||
|
|
||||||
// ValidatedSelectorFromSet returns a Selector which will match exactly the given Set. A
|
// ValidatedSelectorFromSet returns a Selector which will match exactly the given Set. A
|
||||||
// nil and empty Sets are considered equivalent to Everything().
|
// nil and empty Sets are considered equivalent to Everything().
|
||||||
// The Set is validated client-side, which allows to catch errors early.
|
// The Set is validated client-side, which allows to catch errors early.
|
||||||
@ -926,17 +934,19 @@ func ValidatedSelectorFromSet(ls Set) (Selector, error) {
|
|||||||
if ls == nil || len(ls) == 0 {
|
if ls == nil || len(ls) == 0 {
|
||||||
return internalSelector{}, nil
|
return internalSelector{}, nil
|
||||||
}
|
}
|
||||||
requirements := make([]Requirement, 0, len(ls))
|
|
||||||
for label, value := range ls {
|
var allErrs field.ErrorList
|
||||||
r, err := NewRequirement(label, selection.Equals, []string{value})
|
for k, v := range ls {
|
||||||
if err != nil {
|
if err := validateLabelKey(k, keyPath); err != nil {
|
||||||
return nil, err
|
allErrs = append(allErrs, err)
|
||||||
}
|
}
|
||||||
requirements = append(requirements, *r)
|
if err := validateLabelValue(k, v, value0Path); err != nil {
|
||||||
|
allErrs = append(allErrs, err)
|
||||||
}
|
}
|
||||||
// sort to have deterministic string representation
|
}
|
||||||
sort.Sort(ByKey(requirements))
|
|
||||||
return internalSelector(requirements), nil
|
// TODO: validate labels are valid label values
|
||||||
|
return setSelector(ls), allErrs.ToAggregate()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectorFromValidatedSet returns a Selector which will match exactly the given Set.
|
// SelectorFromValidatedSet returns a Selector which will match exactly the given Set.
|
||||||
@ -946,13 +956,7 @@ func SelectorFromValidatedSet(ls Set) Selector {
|
|||||||
if ls == nil || len(ls) == 0 {
|
if ls == nil || len(ls) == 0 {
|
||||||
return internalSelector{}
|
return internalSelector{}
|
||||||
}
|
}
|
||||||
requirements := make([]Requirement, 0, len(ls))
|
return setSelector(ls)
|
||||||
for label, value := range ls {
|
|
||||||
requirements = append(requirements, Requirement{key: label, operator: selection.Equals, strValues: []string{value}})
|
|
||||||
}
|
|
||||||
// sort to have deterministic string representation
|
|
||||||
sort.Sort(ByKey(requirements))
|
|
||||||
return internalSelector(requirements)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseToRequirements takes a string representing a selector and returns a list of
|
// ParseToRequirements takes a string representing a selector and returns a list of
|
||||||
@ -963,3 +967,75 @@ func SelectorFromValidatedSet(ls Set) Selector {
|
|||||||
func ParseToRequirements(selector string, opts ...field.PathOption) ([]Requirement, error) {
|
func ParseToRequirements(selector string, opts ...field.PathOption) ([]Requirement, error) {
|
||||||
return parse(selector, field.ToPath(opts...))
|
return parse(selector, field.ToPath(opts...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type setSelector Set
|
||||||
|
|
||||||
|
func (s setSelector) Matches(labels Labels) bool {
|
||||||
|
for k, v := range s {
|
||||||
|
if !labels.Has(k) || v != labels.Get(k) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) Empty() bool {
|
||||||
|
return len(s) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) String() string {
|
||||||
|
keys := make([]string, 0, len(s))
|
||||||
|
for k := range s {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
// Ensure deterministic output
|
||||||
|
sort.Strings(keys)
|
||||||
|
b := strings.Builder{}
|
||||||
|
for i, key := range keys {
|
||||||
|
last := i == len(keys)-1
|
||||||
|
v := s[key]
|
||||||
|
b.WriteString(key)
|
||||||
|
b.WriteString("=")
|
||||||
|
b.WriteString(v)
|
||||||
|
if !last {
|
||||||
|
b.WriteString(",")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) Add(r ...Requirement) Selector {
|
||||||
|
return s.toFullSelector().Add(r...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) Requirements() (requirements Requirements, selectable bool) {
|
||||||
|
return s.toFullSelector().Requirements()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) DeepCopySelector() Selector {
|
||||||
|
res := make(setSelector, len(s))
|
||||||
|
for k, v := range s {
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) RequiresExactMatch(label string) (value string, found bool) {
|
||||||
|
v, f := s[label]
|
||||||
|
return v, f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s setSelector) toFullSelector() Selector {
|
||||||
|
if s == nil || len(s) == 0 {
|
||||||
|
return internalSelector{}
|
||||||
|
}
|
||||||
|
requirements := make([]Requirement, 0, len(s))
|
||||||
|
for label, value := range s {
|
||||||
|
requirements = append(requirements, Requirement{key: label, operator: selection.Equals, strValues: []string{value}})
|
||||||
|
}
|
||||||
|
// sort to have deterministic string representation
|
||||||
|
sort.Sort(ByKey(requirements))
|
||||||
|
return internalSelector(requirements)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Selector = setSelector{}
|
||||||
|
@ -809,11 +809,48 @@ func BenchmarkSelectorFromValidatedSet(b *testing.B) {
|
|||||||
"foo": "foo",
|
"foo": "foo",
|
||||||
"bar": "bar",
|
"bar": "bar",
|
||||||
}
|
}
|
||||||
|
matchee := Set(map[string]string{
|
||||||
|
"foo": "foo",
|
||||||
|
"bar": "bar",
|
||||||
|
"extra": "label",
|
||||||
|
})
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
if SelectorFromValidatedSet(set).Empty() {
|
s := SelectorFromValidatedSet(set)
|
||||||
|
if s.Empty() {
|
||||||
b.Errorf("Unexpected selector")
|
b.Errorf("Unexpected selector")
|
||||||
}
|
}
|
||||||
|
if !s.Matches(matchee) {
|
||||||
|
b.Errorf("Unexpected match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetSelectorString(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
set Set
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Set{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Set{"app": "foo"},
|
||||||
|
"app=foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Set{"app": "foo", "a": "b"},
|
||||||
|
"a=b,app=foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.out, func(t *testing.T) {
|
||||||
|
if got := setSelector(tt.set).String(); tt.out != got {
|
||||||
|
t.Fatalf("expected %v, got %v", tt.out, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -899,19 +936,13 @@ func TestValidatedSelectorFromSet(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input Set
|
input Set
|
||||||
expectedSelector internalSelector
|
expectedSelector Selector
|
||||||
expectedError field.ErrorList
|
expectedError field.ErrorList
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Simple Set, no error",
|
name: "Simple Set, no error",
|
||||||
input: Set{"key": "val"},
|
input: Set{"key": "val"},
|
||||||
expectedSelector: internalSelector{
|
expectedSelector: setSelector{"key": "val"},
|
||||||
Requirement{
|
|
||||||
key: "key",
|
|
||||||
operator: selection.Equals,
|
|
||||||
strValues: []string{"val"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Set, value too long",
|
name: "Invalid Set, value too long",
|
||||||
|
Loading…
Reference in New Issue
Block a user