From a9d4ee4f828f2537c5fa7189917fabc740aa8306 Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Wed, 14 Sep 2022 20:13:08 +0200 Subject: [PATCH] internal/transport: optimize grpc-message encoding/decoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use strings.Builder (introduced in Go 1.10) instead of bytes.Buffer. Also call fmt.Fprintf directly on the builder. This improves performance both in terms of CPU and memory consumption: name old time/op new time/op delta DecodeGrpcMessage-8 345ns ± 5% 225ns ± 4% -34.56% (p=0.000 n=20+20) EncodeGrpcMessage-8 1.33µs ± 4% 1.26µs ± 3% -5.76% (p=0.000 n=20+20) name old alloc/op new alloc/op delta DecodeGrpcMessage-8 80.0B ± 0% 24.0B ± 0% -70.00% (p=0.000 n=20+20) EncodeGrpcMessage-8 115B ± 0% 88B ± 0% -23.48% (p=0.000 n=20+20) name old allocs/op new allocs/op delta DecodeGrpcMessage-8 2.00 ± 0% 2.00 ± 0% ~ (all equal) EncodeGrpcMessage-8 8.00 ± 0% 4.00 ± 0% -50.00% (p=0.000 n=20+20) --- internal/transport/http_util.go | 21 ++++++++++----------- internal/transport/http_util_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/internal/transport/http_util.go b/internal/transport/http_util.go index d632dc4e812..2c601a864d9 100644 --- a/internal/transport/http_util.go +++ b/internal/transport/http_util.go @@ -20,7 +20,6 @@ package transport import ( "bufio" - "bytes" "encoding/base64" "fmt" "io" @@ -251,13 +250,13 @@ func encodeGrpcMessage(msg string) string { } func encodeGrpcMessageUnchecked(msg string) string { - var buf bytes.Buffer + var sb strings.Builder for len(msg) > 0 { r, size := utf8.DecodeRuneInString(msg) for _, b := range []byte(string(r)) { if size > 1 { // If size > 1, r is not ascii. Always do percent encoding. - buf.WriteString(fmt.Sprintf("%%%02X", b)) + fmt.Fprintf(&sb, "%%%02X", b) continue } @@ -266,14 +265,14 @@ func encodeGrpcMessageUnchecked(msg string) string { // // fmt.Sprintf("%%%02X", utf8.RuneError) gives "%FFFD". if b >= spaceByte && b <= tildeByte && b != percentByte { - buf.WriteByte(b) + sb.WriteByte(b) } else { - buf.WriteString(fmt.Sprintf("%%%02X", b)) + fmt.Fprintf(&sb, "%%%02X", b) } } msg = msg[size:] } - return buf.String() + return sb.String() } // decodeGrpcMessage decodes the msg encoded by encodeGrpcMessage. @@ -291,23 +290,23 @@ func decodeGrpcMessage(msg string) string { } func decodeGrpcMessageUnchecked(msg string) string { - var buf bytes.Buffer + var sb strings.Builder lenMsg := len(msg) for i := 0; i < lenMsg; i++ { c := msg[i] if c == percentByte && i+2 < lenMsg { parsed, err := strconv.ParseUint(msg[i+1:i+3], 16, 8) if err != nil { - buf.WriteByte(c) + sb.WriteByte(c) } else { - buf.WriteByte(byte(parsed)) + sb.WriteByte(byte(parsed)) i += 2 } } else { - buf.WriteByte(c) + sb.WriteByte(c) } } - return buf.String() + return sb.String() } type bufWriter struct { diff --git a/internal/transport/http_util_test.go b/internal/transport/http_util_test.go index bbd53180471..cc7807670b6 100644 --- a/internal/transport/http_util_test.go +++ b/internal/transport/http_util_test.go @@ -214,3 +214,27 @@ func (s) TestParseDialTarget(t *testing.T) { } } } + +func BenchmarkDecodeGrpcMessage(b *testing.B) { + input := "Hello, %E4%B8%96%E7%95%8C" + want := "Hello, 世界" + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got := decodeGrpcMessage(input) + if got != want { + b.Fatalf("decodeGrpcMessage(%q) = %s, want %s", input, got, want) + } + } +} + +func BenchmarkEncodeGrpcMessage(b *testing.B) { + input := "Hello, 世界" + want := "Hello, %E4%B8%96%E7%95%8C" + b.ReportAllocs() + for i := 0; i < b.N; i++ { + got := encodeGrpcMessage(input) + if got != want { + b.Fatalf("encodeGrpcMessage(%q) = %s, want %s", input, got, want) + } + } +}