Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce fmt.Sprintf allocations in query encoding #2919

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 6 additions & 19 deletions aws/protocol/query/array.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,8 @@ type Array struct {
// keys for each element in the list. For example, an entry might have the
// key "ParentStructure.ListName.member.MemberName.1".
//
// While this is currently represented as a string that gets added to, it
// could also be represented as a stack that only gets condensed into a
// string when a finalized key is created. This could potentially reduce
// allocations.
// When the array is not flat the prefix will contain the memberName otherwise the memberName is ignored
prefix string
// Whether the list is flat or not. A list that is not flat will produce the
// following entry to the url.Values for a given entry:
// ListName.MemberName.1=value
// A list that is flat will produce the following:
// ListName.1=value
flat bool
// The location name of the member. In most cases this should be "member".
memberName string
// Elements are stored in values, so we keep track of the list size here.
size int32
// Empty lists are encoded as "<prefix>=", if we add a value later we will
Expand All @@ -45,11 +34,13 @@ func newArray(values url.Values, prefix string, flat bool, memberName string) *A
emptyValue := newValue(values, prefix, flat)
emptyValue.String("")

if !flat {
prefix = prefix + keySeparator + memberName
}

return &Array{
values: values,
prefix: prefix,
flat: flat,
memberName: memberName,
emptyValue: emptyValue,
}
}
Expand All @@ -63,10 +54,6 @@ func (a *Array) Value() Value {

// Query lists start a 1, so adjust the size first
a.size++
prefix := a.prefix
if !a.flat {
prefix = fmt.Sprintf("%s.%s", prefix, a.memberName)
}
// Lists can't have flat members
return newValue(a.values, fmt.Sprintf("%s.%d", prefix, a.size), false)
return newValue(a.values, fmt.Sprintf("%s.%d", a.prefix, a.size), false)
}
9 changes: 3 additions & 6 deletions aws/protocol/query/object.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package query

import (
"fmt"
"net/url"
)
import "net/url"

// Object represents the encoding of Query structures and unions. A Query
// object is a representation of a mapping of string keys to arbitrary
Expand Down Expand Up @@ -56,14 +53,14 @@ func (o *Object) FlatKey(name string) Value {

func (o *Object) key(name string, flatValue bool) Value {
if o.prefix != "" {
return newValue(o.values, fmt.Sprintf("%s.%s", o.prefix, name), flatValue)
return newValue(o.values, o.prefix+keySeparator+name, flatValue)
}
return newValue(o.values, name, flatValue)
}

func (o *Object) keyWithValues(name string, flatValue bool) Value {
if o.prefix != "" {
return newAppendValue(o.values, fmt.Sprintf("%s.%s", o.prefix, name), flatValue)
return newAppendValue(o.values, o.prefix+keySeparator+name, flatValue)
}
return newAppendValue(o.values, name, flatValue)
}
2 changes: 2 additions & 0 deletions aws/protocol/query/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"github.com/aws/smithy-go/encoding/httpbinding"
)

const keySeparator = "."

// Value represents a Query Value type.
type Value struct {
// The query values to add the value to.
Expand Down
64 changes: 64 additions & 0 deletions aws/protocol/query/value_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package query

import (
"fmt"
"strconv"
"testing"
)

var output string

func Benchmark_sprintf_strings(b *testing.B) {
for i := 0; i < b.N; i++ {
output = fmt.Sprintf("%s.%s", "foo", "bar")
}
}

func Benchmark_concat_strings(b *testing.B) {
for i := 0; i < b.N; i++ {
output = "foo" + keySeparator + "bar"
}
}

func Benchmark_int_formatting(b *testing.B) {
benchmarkFuncs := []struct {
name string
formatter func(val int32)
}{
{
name: "array - sprintf", formatter: func(val int32) {
output = fmt.Sprintf("%s.%d", "foo", val)
},
},
{
name: "array - concat strconv", formatter: func(val int32) {
output = "foo" + keySeparator + strconv.FormatInt(int64(val), 10)
},
},
{
name: "map - sprintf", formatter: func(val int32) {
output = fmt.Sprintf("%s.%d.%s", "foo", val, "bar")
output = fmt.Sprintf("%s.%d.%s", "foo", val, "bar")
},
},
{
name: "map - concat strconv", formatter: func(val int32) {
valString := strconv.FormatInt(int64(val), 10)
output = "foo" + keySeparator + valString + keySeparator + "bar"
output = "foo" + keySeparator + valString + keySeparator + "bar"
},
},
}

sizesToTest := []int32{1, 10, 100, 250, 500, 1000}

for _, bm := range benchmarkFuncs {
for _, size := range sizesToTest {
b.Run(fmt.Sprintf("%s with %d size", bm.name, size), func(b *testing.B) {
for i := 0; i < b.N; i++ {
bm.formatter(size)
}
})
}
}
}