From b3561401fd1ad7ba849de85746dca7c34c4509f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Martins?= Date: Mon, 28 Dec 2020 14:54:40 +0100 Subject: [PATCH 1/2] pkg/util/net: replace bytes.Buffer with strings.Builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit strings.Builder has better performance over bytes.Buffer for building strings: Benchmark results: ``` name old time/op new time/op delta _ParseQuotedString-8 146ns ±20% 105ns ± 2% -28.14% (p=0.008 n=5+5) name old alloc/op new alloc/op delta _ParseQuotedString-8 80.0B ± 0% 24.0B ± 0% -70.00% (p=0.008 n=5+5) name old allocs/op new allocs/op delta _ParseQuotedString-8 2.00 ± 0% 2.00 ± 0% ~ (all equal) ``` Signed-off-by: André Martins --- .../k8s.io/apimachinery/pkg/util/net/http.go | 2 +- .../apimachinery/pkg/util/net/http_test.go | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/http.go b/staging/src/k8s.io/apimachinery/pkg/util/net/http.go index ba63d02df69..567a294e26c 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/http.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/http.go @@ -693,7 +693,7 @@ func parseQuotedString(quotedString string) (string, string, error) { var remainder string escaping := false closedQuote := false - result := &bytes.Buffer{} + result := &strings.Builder{} loop: for i := 0; i < len(quotedString); i++ { b := quotedString[i] diff --git a/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go b/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go index a43161b88b7..9411bfa7ddf 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go @@ -1107,3 +1107,21 @@ func TestPingTimeoutSeconds(t *testing.T) { } reset() } + +func Benchmark_ParseQuotedString(b *testing.B) { + str := `"The quick brown" fox jumps over the lazy dog` + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + quoted, remainder, err := parseQuotedString(str) + if err != nil { + b.Errorf("Unexpected error %s", err) + } + if quoted != "The quick brown" { + b.Errorf("Unexpected quoted string %s", quoted) + } + if remainder != "fox jumps over the lazy dog" { + b.Errorf("Unexpected remainder string %s", quoted) + } + } +} From 659f9d383ad4ed16399c39fec394c601dff80101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Martins?= Date: Mon, 28 Dec 2020 14:59:45 +0100 Subject: [PATCH 2/2] pkg/labels: replace bytes.Buffer with strings.Builder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../apimachinery/pkg/labels/selector.go | 40 +++++++++++-------- .../apimachinery/pkg/labels/selector_test.go | 18 +++++++++ 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/staging/src/k8s.io/apimachinery/pkg/labels/selector.go b/staging/src/k8s.io/apimachinery/pkg/labels/selector.go index e5f1a826403..ab12f7c2f32 100644 --- a/staging/src/k8s.io/apimachinery/pkg/labels/selector.go +++ b/staging/src/k8s.io/apimachinery/pkg/labels/selector.go @@ -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 diff --git a/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go b/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go index aa3fb2e54bc..f2cb7f8941a 100644 --- a/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/labels/selector_test.go @@ -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") + } + } +}