pkg/labels: replace bytes.Buffer with strings.Builder

strings.Builder has better performance over bytes.Buffer for building
strings:

Benchmark results:
```
name                 old time/op    new time/op    delta
RequirementString-8    92.6ns ± 1%    46.9ns ± 1%  -49.34%  (p=0.000 n=4+5)

name                 old alloc/op   new alloc/op   delta
RequirementString-8     96.0B ± 0%     32.0B ± 0%  -66.67%  (p=0.008 n=5+5)

name                 old allocs/op  new allocs/op  delta
RequirementString-8      2.00 ± 0%      1.00 ± 0%  -50.00%  (p=0.008 n=5+5)
```

Signed-off-by: André Martins <aanm90@gmail.com>
This commit is contained in:
André Martins 2020-12-28 14:59:45 +01:00
parent b3561401fd
commit 659f9d383a
2 changed files with 41 additions and 17 deletions

View File

@ -17,7 +17,6 @@ limitations under the License.
package labels
import (
"bytes"
"fmt"
"sort"
"strconv"
@ -274,48 +273,55 @@ func (s internalSelector) Empty() bool {
// Requirement. If called on an invalid Requirement, an error is
// returned. See NewRequirement for creating a valid Requirement.
func (r *Requirement) String() string {
var buffer bytes.Buffer
var sb strings.Builder
sb.Grow(
// length of r.key
len(r.key) +
// length of 'r.operator' + 2 spaces for the worst case ('in' and 'notin')
len(r.operator) + 2 +
// length of 'r.strValues' slice times. Heuristically 5 chars per word
+5*len(r.strValues))
if r.operator == selection.DoesNotExist {
buffer.WriteString("!")
sb.WriteString("!")
}
buffer.WriteString(r.key)
sb.WriteString(r.key)
switch r.operator {
case selection.Equals:
buffer.WriteString("=")
sb.WriteString("=")
case selection.DoubleEquals:
buffer.WriteString("==")
sb.WriteString("==")
case selection.NotEquals:
buffer.WriteString("!=")
sb.WriteString("!=")
case selection.In:
buffer.WriteString(" in ")
sb.WriteString(" in ")
case selection.NotIn:
buffer.WriteString(" notin ")
sb.WriteString(" notin ")
case selection.GreaterThan:
buffer.WriteString(">")
sb.WriteString(">")
case selection.LessThan:
buffer.WriteString("<")
sb.WriteString("<")
case selection.Exists, selection.DoesNotExist:
return buffer.String()
return sb.String()
}
switch r.operator {
case selection.In, selection.NotIn:
buffer.WriteString("(")
sb.WriteString("(")
}
if len(r.strValues) == 1 {
buffer.WriteString(r.strValues[0])
sb.WriteString(r.strValues[0])
} else { // only > 1 since == 0 prohibited by NewRequirement
// normalizes value order on output, without mutating the in-memory selector representation
// also avoids normalization when it is not required, and ensures we do not mutate shared data
buffer.WriteString(strings.Join(safeSort(r.strValues), ","))
sb.WriteString(strings.Join(safeSort(r.strValues), ","))
}
switch r.operator {
case selection.In, selection.NotIn:
buffer.WriteString(")")
sb.WriteString(")")
}
return buffer.String()
return sb.String()
}
// safeSort sorts input strings without modification

View File

@ -738,3 +738,21 @@ func TestValidatedSelectorFromSet(t *testing.T) {
}
}
}
func BenchmarkRequirementString(b *testing.B) {
r := Requirement{
key: "environment",
operator: selection.NotIn,
strValues: []string{
"dev",
},
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
if r.String() != "environment notin (dev)" {
b.Errorf("Unexpected Requirement string")
}
}
}