]> Cypherpunks repositories - gostls13.git/commitdiff
encoding/base32: increase performance and code reuse
authorSven Taute <sven.taute@gmail.com>
Mon, 9 Sep 2019 21:56:46 +0000 (21:56 +0000)
committerIan Lance Taylor <iant@golang.org>
Wed, 11 Sep 2019 00:56:39 +0000 (00:56 +0000)
Add benchmarks for the Encode/Decode functions operating on []byte and increase decoding performance by removing the calls to strings.Map/bytes.Map and reusing the newline filtering code that is used by NewDecoder.
Cut allocations in half for DecodeString.

Comparison using the new benchmarks:
name            old time/op    new time/op     delta
Encode            16.7µs ± 1%     17.0µs ± 2%    +2.25%  (p=0.000 n=9+9)
EncodeToString    21.1µs ± 1%     20.9µs ± 1%    -0.96%  (p=0.000 n=10+10)
Decode             141µs ± 1%       54µs ± 1%   -61.51%  (p=0.000 n=10+10)
DecodeString      81.4µs ± 0%     54.7µs ± 1%   -32.79%  (p=0.000 n=9+10)

name            old speed      new speed       delta
Encode           492MB/s ± 1%    481MB/s ± 2%    -2.19%  (p=0.000 n=9+9)
EncodeToString   389MB/s ± 1%    392MB/s ± 1%    +0.97%  (p=0.000 n=10+10)
Decode          93.0MB/s ± 1%  241.6MB/s ± 1%  +159.82%  (p=0.000 n=10+10)
DecodeString     161MB/s ± 0%    240MB/s ± 1%   +48.78%  (p=0.000 n=9+10)

Change-Id: Id53633514a9e14ecd0389d52114b2b8ca64370cb
GitHub-Last-Rev: f4be3cf55caf5b89d76d14b7f32422faff39e3c3
GitHub-Pull-Request: golang/go#30376
Reviewed-on: https://go-review.googlesource.com/c/go/+/163598
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Lance Taylor <iant@golang.org>
src/encoding/base32/base32.go
src/encoding/base32/base32_test.go

index 7b74c1ab2cfd17955b41fd4028cab167596434e1..2f7d3637e5a85329e97474310e34c54f25053ead 100644 (file)
@@ -6,10 +6,8 @@
 package base32
 
 import (
-       "bytes"
        "io"
        "strconv"
-       "strings"
 )
 
 /*
@@ -62,13 +60,6 @@ var StdEncoding = NewEncoding(encodeStd)
 // It is typically used in DNS.
 var HexEncoding = NewEncoding(encodeHex)
 
-var removeNewlinesMapper = func(r rune) rune {
-       if r == '\r' || r == '\n' {
-               return -1
-       }
-       return r
-}
-
 // WithPadding creates a new encoding identical to enc except
 // with a specified padding character, or NoPadding to disable padding.
 // The padding character must not be '\r' or '\n', must not
@@ -372,17 +363,18 @@ func (enc *Encoding) decode(dst, src []byte) (n int, end bool, err error) {
 // number of bytes successfully written and CorruptInputError.
 // New line characters (\r and \n) are ignored.
 func (enc *Encoding) Decode(dst, src []byte) (n int, err error) {
-       src = bytes.Map(removeNewlinesMapper, src)
-       n, _, err = enc.decode(dst, src)
+       buf := make([]byte, len(src))
+       l := stripNewlines(buf, src)
+       n, _, err = enc.decode(dst, buf[:l])
        return
 }
 
 // DecodeString returns the bytes represented by the base32 string s.
 func (enc *Encoding) DecodeString(s string) ([]byte, error) {
-       s = strings.Map(removeNewlinesMapper, s)
-       dbuf := make([]byte, enc.DecodedLen(len(s)))
-       n, _, err := enc.decode(dbuf, []byte(s))
-       return dbuf[:n], err
+       buf := []byte(s)
+       l := stripNewlines(buf, buf)
+       n, _, err := enc.decode(buf, buf[:l])
+       return buf[:n], err
 }
 
 type decoder struct {
@@ -497,18 +489,25 @@ type newlineFilteringReader struct {
        wrapped io.Reader
 }
 
+// stripNewlines removes newline characters and returns the number
+// of non-newline characters copied to dst.
+func stripNewlines(dst, src []byte) int {
+       offset := 0
+       for _, b := range src {
+               if b == '\r' || b == '\n' {
+                       continue
+               }
+               dst[offset] = b
+               offset++
+       }
+       return offset
+}
+
 func (r *newlineFilteringReader) Read(p []byte) (int, error) {
        n, err := r.wrapped.Read(p)
        for n > 0 {
-               offset := 0
-               for i, b := range p[0:n] {
-                       if b != '\r' && b != '\n' {
-                               if i != offset {
-                                       p[offset] = b
-                               }
-                               offset++
-                       }
-               }
+               s := p[0:n]
+               offset := stripNewlines(s, s)
                if err != nil || offset > 0 {
                        return offset, err
                }
index eb14f1eb26a8a5566bb2c02b8a72f6c05624269f..0b611db0b24054df47b5b7057489560daf5cc956 100644 (file)
@@ -445,6 +445,15 @@ LNEBUWIIDFON2CA3DBMJXXE5LNFY==
        }
 }
 
+func BenchmarkEncode(b *testing.B) {
+       data := make([]byte, 8192)
+       buf := make([]byte, StdEncoding.EncodedLen(len(data)))
+       b.SetBytes(int64(len(data)))
+       for i := 0; i < b.N; i++ {
+               StdEncoding.Encode(buf, data)
+       }
+}
+
 func BenchmarkEncodeToString(b *testing.B) {
        data := make([]byte, 8192)
        b.SetBytes(int64(len(data)))
@@ -453,6 +462,15 @@ func BenchmarkEncodeToString(b *testing.B) {
        }
 }
 
+func BenchmarkDecode(b *testing.B) {
+       data := make([]byte, StdEncoding.EncodedLen(8192))
+       StdEncoding.Encode(data, make([]byte, 8192))
+       buf := make([]byte, 8192)
+       b.SetBytes(int64(len(data)))
+       for i := 0; i < b.N; i++ {
+               StdEncoding.Decode(buf, data)
+       }
+}
 func BenchmarkDecodeString(b *testing.B) {
        data := StdEncoding.EncodeToString(make([]byte, 8192))
        b.SetBytes(int64(len(data)))