From 73990df675bff6b209941427540b43782675493f Mon Sep 17 00:00:00 2001 From: tdakkota Date: Tue, 31 Jan 2023 15:05:57 +0300 Subject: [PATCH 1/5] test: add `BenchmarkSkipError` --- enc_stream_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/enc_stream_test.go b/enc_stream_test.go index 3eab255..8564370 100644 --- a/enc_stream_test.go +++ b/enc_stream_test.go @@ -101,3 +101,16 @@ func TestEncoder_ResetWriter(t *testing.T) { require.Equal(t, expected, got.String()) } } + +// This benchmark is used to measure the overhead of ignoring errors. +func BenchmarkSkipError(b *testing.B) { + e := NewStreamingEncoder(io.Discard, 32) + e.w.stream.writeErr = errors.New("test") + + b.ResetTimer() + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + encodeObject(e) + } +} From f0619b7afeed72c070264de3e64d9612d2f3346b Mon Sep 17 00:00:00 2001 From: tdakkota Date: Tue, 31 Jan 2023 15:24:44 +0300 Subject: [PATCH 2/5] perf: fail early in string encoders --- w_str.go | 14 +++++++------- w_str_escape.go | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/w_str.go b/w_str.go index 1b6aa4f..88684c0 100644 --- a/w_str.go +++ b/w_str.go @@ -45,18 +45,18 @@ func writeStr[S byteseq.Byteseq](w *Writer, v S) (fail bool) { var ( i = 0 length = len(v) - c byte ) - for i, c = range []byte(v) { + for ; i < length && !fail; i++ { + c := v[i] if safeSet[c] != 0 { - goto slow + break } } - if i == length-1 { - return writeStreamByteseq(w, v) || w.byte('"') + fail = fail || writeStreamByteseq(w, v[:i]) + if i == length { + return fail || w.byte('"') } -slow: - return writeStreamByteseq(w, v[:i]) || strSlow[S](w, v[i:]) + return fail || strSlow[S](w, v[i:]) } func strSlow[S byteseq.Byteseq](w *Writer, v S) (fail bool) { diff --git a/w_str_escape.go b/w_str_escape.go index 14bf3b2..b731f86 100644 --- a/w_str_escape.go +++ b/w_str_escape.go @@ -124,12 +124,13 @@ func (w *Writer) ByteStrEscape(v []byte) bool { func strEscape[S byteseq.Byteseq](w *Writer, v S) (fail bool) { fail = w.byte('"') + // Fast path, probably does not require escaping. var ( i = 0 length = len(v) ) - for ; i < length; i++ { + for ; i < length && !fail; i++ { c := v[i] if c >= utf8.RuneSelf || !(htmlSafeSet[c]) { break From 7d9238355e04d71e8d2660a874866189206f5a00 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Wed, 1 Feb 2023 15:00:02 +0300 Subject: [PATCH 3/5] perf: fail early in `writeStreamByteseq` --- w_stream.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/w_stream.go b/w_stream.go index a2398d5..eb86e25 100644 --- a/w_stream.go +++ b/w_stream.go @@ -73,6 +73,10 @@ func writeStreamByteseq[S byteseq.Byteseq](w *Writer, s S) bool { return false } + if w.stream.writeErr != nil { + return true + } + for len(w.Buf)+len(s) > cap(w.Buf) { var fail bool w.Buf, fail = w.stream.flush(w.Buf) From 5cd552bbe869c20b18b8557187b6c27bdc46b99d Mon Sep 17 00:00:00 2001 From: tdakkota Date: Wed, 1 Feb 2023 15:05:20 +0300 Subject: [PATCH 4/5] perf: fail early in float encoders --- float_test.go | 11 +++++++++++ w_float_bits.go | 13 ++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/float_test.go b/float_test.go index d3cf615..77fb2de 100644 --- a/float_test.go +++ b/float_test.go @@ -10,6 +10,8 @@ import ( "testing" "github.com/stretchr/testify/require" + + "github.com/go-faster/errors" ) // epsilon to compare floats. @@ -152,6 +154,15 @@ func TestWriteFloat64(t *testing.T) { should.Equal("1e-7", e.String()) } +func TestEncoder_FloatError(t *testing.T) { + e := NewStreamingEncoder(io.Discard, -1) + e.w.stream.setError(errors.New("foo")) + + require.True(t, e.Float32(10)) + require.True(t, e.Float64(10)) + require.Error(t, e.Close()) +} + func TestDecoder_FloatEOF(t *testing.T) { d := GetDecoder() diff --git a/w_float_bits.go b/w_float_bits.go index 652b38e..b88f6c5 100644 --- a/w_float_bits.go +++ b/w_float_bits.go @@ -20,14 +20,17 @@ func (w *Writer) Float(v float64, bits int) bool { return w.Null() } - if w.stream == nil { + switch s := w.stream; { + case s == nil: w.Buf = floatAppend(w.Buf, v, bits) return false + case s.fail(): + return true + default: + tmp := make([]byte, 0, 32) + tmp = floatAppend(tmp, v, bits) + return writeStreamByteseq(w, tmp) } - - tmp := make([]byte, 0, 32) - tmp = floatAppend(tmp, v, bits) - return writeStreamByteseq(w, tmp) } func floatAppend(b []byte, v float64, bits int) []byte { From e0930c1c4e98a362f50bc88d303e9a091347c727 Mon Sep 17 00:00:00 2001 From: tdakkota Date: Wed, 1 Feb 2023 15:06:01 +0300 Subject: [PATCH 5/5] refactor: set the error using a setter --- enc_stream_test.go | 2 +- w_b64.go | 4 ++-- w_stream.go | 20 ++++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/enc_stream_test.go b/enc_stream_test.go index 8564370..14a5810 100644 --- a/enc_stream_test.go +++ b/enc_stream_test.go @@ -105,7 +105,7 @@ func TestEncoder_ResetWriter(t *testing.T) { // This benchmark is used to measure the overhead of ignoring errors. func BenchmarkSkipError(b *testing.B) { e := NewStreamingEncoder(io.Discard, 32) - e.w.stream.writeErr = errors.New("test") + e.w.stream.setError(errors.New("test")) b.ResetTimer() b.ReportAllocs() diff --git a/w_b64.go b/w_b64.go index af5cc65..6286c0f 100644 --- a/w_b64.go +++ b/w_b64.go @@ -34,11 +34,11 @@ func (w *Writer) Base64(data []byte) bool { } e := stdbase64.NewEncoder(stdbase64.StdEncoding, s.writer) if _, err := e.Write(data); err != nil { - s.writeErr = err + s.setError(err) return true } if err := e.Close(); err != nil { - s.writeErr = err + s.setError(err) return true } } diff --git a/w_stream.go b/w_stream.go index eb86e25..0cf2a06 100644 --- a/w_stream.go +++ b/w_stream.go @@ -44,18 +44,26 @@ func (s *streamState) Reset(w io.Writer) { s.writeErr = nil } +func (s *streamState) setError(err error) { + s.writeErr = err +} + +func (s *streamState) fail() bool { + return s.writeErr != nil +} + func (s *streamState) flush(buf []byte) ([]byte, bool) { - if s.writeErr != nil { + if s.fail() { return nil, true } - var n int - n, s.writeErr = s.writer.Write(buf) + n, err := s.writer.Write(buf) switch { - case s.writeErr != nil: + case err != nil: + s.setError(err) return nil, true case n != len(buf): - s.writeErr = io.ErrShortWrite + s.setError(io.ErrShortWrite) return nil, true default: buf = buf[:0] @@ -73,7 +81,7 @@ func writeStreamByteseq[S byteseq.Byteseq](w *Writer, s S) bool { return false } - if w.stream.writeErr != nil { + if w.stream.fail() { return true }