From 5b2a731963d9337b33af724bc13190f8fe25a81b Mon Sep 17 00:00:00 2001 From: Tao Wen Date: Sun, 15 Jan 2017 17:30:02 +0800 Subject: [PATCH] implement readFloat32 fast path --- feature_iter_float.go | 156 +++++++++++++++++++++++++++++++++++++++ iterator.go | 71 +----------------- jsoniter_float_test.go | 31 ++++++-- jsoniter_reflect_test.go | 2 +- 4 files changed, 181 insertions(+), 79 deletions(-) create mode 100644 feature_iter_float.go diff --git a/feature_iter_float.go b/feature_iter_float.go new file mode 100644 index 0000000..e55fb6e --- /dev/null +++ b/feature_iter_float.go @@ -0,0 +1,156 @@ +package jsoniter + +import ( + "io" + "strconv" + "unsafe" +) + +var zeroToNineDigits []int8 +const invalidCharForNumber = int8(-1) +const endOfNumber = int8(-2) +const dotInNumber = int8(-3) +const safeToMultiple10 = uint64(0xffffffffffffffff) / 10 - 10 + +func init() { + zeroToNineDigits = make([]int8, 256) + for i := 0; i < len(zeroToNineDigits); i++ { + zeroToNineDigits[i] = invalidCharForNumber + } + for i := int8('0'); i < int8('9'); i++ { + zeroToNineDigits[i] = i - int8('0') + } + zeroToNineDigits[','] = endOfNumber; + zeroToNineDigits[']'] = endOfNumber; + zeroToNineDigits['}'] = endOfNumber; + zeroToNineDigits[' '] = endOfNumber; + zeroToNineDigits['.'] = dotInNumber; +} + +func (iter *Iterator) ReadFloat32() (ret float32) { + c := iter.nextToken() + if c == '-' { + return -iter.readPositiveFloat32() + } else { + iter.unreadByte() + return iter.readPositiveFloat32() + } +} + +func (iter *Iterator) readPositiveFloat32() (ret float32) { + value := uint64(0) + c := byte(' ') + i := iter.head + non_decimal_loop: + for ; i < iter.tail; i++ { + c = iter.buf[i] + ind := zeroToNineDigits[c] + switch ind { + case invalidCharForNumber: + return iter.readFloat32SlowPath() + case endOfNumber: + iter.head = i + return float32(value) + case dotInNumber: + break non_decimal_loop + } + if value > safeToMultiple10 { + return iter.readFloat32SlowPath() + } + value = (value << 3) + (value << 1) + uint64(ind); // value = value * 10 + ind; + } + if c == '.' { + i++ + decimalPlaces := 0; + for ; i < iter.tail; i++ { + c = iter.buf[i] + ind := zeroToNineDigits[c]; + switch ind { + case endOfNumber: + if decimalPlaces > 0 && decimalPlaces < len(POW10) { + iter.head = i + return float32(float64(value) / float64(POW10[decimalPlaces])) + } + // too many decimal places + return iter.readFloat32SlowPath() + case invalidCharForNumber: + fallthrough + case dotInNumber: + return iter.readFloat32SlowPath() + } + decimalPlaces++ + if value > safeToMultiple10 { + return iter.readFloat32SlowPath() + } + value = (value << 3) + (value << 1) + uint64(ind) + } + } + return iter.readFloat32SlowPath() +} + +func (iter *Iterator) readFloat32SlowPath() (ret float32) { + strBuf := [16]byte{} + str := strBuf[0:0] + hasMore := true + for hasMore { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + str = append(str, c) + continue + default: + hasMore = false + break + } + } + if hasMore { + if !iter.loadMore() { + break + } + } + } + if iter.Error != nil && iter.Error != io.EOF { + return + } + val, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&str)), 32) + if err != nil { + iter.Error = err + return + } + return float32(val) +} + +// ReadFloat64 reads a json object as Float64 +func (iter *Iterator) ReadFloat64() (ret float64) { + strBuf := [8]byte{} + str := strBuf[0:0] + hasMore := true + for hasMore { + for i := iter.head; i < iter.tail; i++ { + c := iter.buf[i] + switch c { + case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + str = append(str, c) + continue + default: + hasMore = false + break + } + } + if hasMore { + if !iter.loadMore() { + break + } + } + } + if iter.Error != nil && iter.Error != io.EOF { + return + } + val, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&str)), 64) + if err != nil { + iter.Error = err + return + } + return val +} diff --git a/iterator.go b/iterator.go index b400626..9eef214 100644 --- a/iterator.go +++ b/iterator.go @@ -4,9 +4,7 @@ import ( "encoding/base64" "fmt" "io" - "strconv" "unicode/utf16" - "unsafe" ) type ValueType int @@ -71,7 +69,7 @@ type Iterator struct { } // Create creates an empty Iterator instance -func Create() *Iterator { +func NewIterator() *Iterator { return &Iterator{ reader: nil, buf: nil, @@ -570,73 +568,6 @@ func (iter *Iterator) ReadArray() (ret bool) { } } -// ReadFloat32 reads a json object as Float32 -func (iter *Iterator) ReadFloat32() (ret float32) { - strBuf := [8]byte{} - str := strBuf[0:0] - hasMore := true - for hasMore { - for i := iter.head; i < iter.tail; i++ { - c := iter.buf[i] - switch c { - case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - str = append(str, c) - continue - default: - hasMore = false - break - } - } - if hasMore { - if !iter.loadMore() { - break - } - } - } - if iter.Error != nil && iter.Error != io.EOF { - return - } - val, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&str)), 32) - if err != nil { - iter.Error = err - return - } - return float32(val) -} - -// ReadFloat64 reads a json object as Float64 -func (iter *Iterator) ReadFloat64() (ret float64) { - strBuf := [8]byte{} - str := strBuf[0:0] - hasMore := true - for hasMore { - for i := iter.head; i < iter.tail; i++ { - c := iter.buf[i] - switch c { - case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - str = append(str, c) - continue - default: - hasMore = false - break - } - } - if hasMore { - if !iter.loadMore() { - break - } - } - } - if iter.Error != nil && iter.Error != io.EOF { - return - } - val, err := strconv.ParseFloat(*(*string)(unsafe.Pointer(&str)), 64) - if err != nil { - iter.Error = err - return - } - return val -} // ReadBool reads a json object as Bool func (iter *Iterator) ReadBool() (ret bool) { diff --git a/jsoniter_float_test.go b/jsoniter_float_test.go index 5c8862d..802f1fc 100644 --- a/jsoniter_float_test.go +++ b/jsoniter_float_test.go @@ -25,12 +25,25 @@ func Test_float64_1_dot_1(t *testing.T) { } } -func Test_float32_1_dot_1_comma(t *testing.T) { - iter := ParseString(`1.1,`) - val := iter.ReadFloat32() - if val != 1.1 { - fmt.Println(iter.Error) - t.Fatal(val) +func Test_read_float32(t *testing.T) { + inputs := []string{`1.1`, `1000`, `9223372036854775807`, `12.3`, `-12.3`, `720368.54775807`, `720368.547758075`} + for _, input := range inputs { + // non-streaming + t.Run(fmt.Sprintf("%v", input), func(t *testing.T) { + should := require.New(t) + iter := ParseString(input + ",") + expected, err := strconv.ParseFloat(input, 32) + should.Nil(err) + should.Equal(float32(expected), iter.ReadFloat32()) + }) + // streaming + t.Run(fmt.Sprintf("%v", input), func(t *testing.T) { + should := require.New(t) + iter := Parse(bytes.NewBufferString(input + ","), 2) + expected, err := strconv.ParseFloat(input, 32) + should.Nil(err) + should.Equal(float32(expected), iter.ReadFloat32()) + }) } } @@ -102,9 +115,11 @@ func Test_write_float64(t *testing.T) { func Benchmark_jsoniter_float(b *testing.B) { b.ReportAllocs() + input := []byte(`1.1123,`) + iter := NewIterator() for n := 0; n < b.N; n++ { - iter := ParseString(`1.1111111111`) - iter.ReadFloat64() + iter.ResetBytes(input) + iter.ReadFloat32() } } diff --git a/jsoniter_reflect_test.go b/jsoniter_reflect_test.go index f2628f9..578a85e 100644 --- a/jsoniter_reflect_test.go +++ b/jsoniter_reflect_test.go @@ -70,7 +70,7 @@ type StructOfTagOne struct { func Benchmark_jsoniter_reflect(b *testing.B) { b.ReportAllocs() - iter := Create() + iter := NewIterator() Struct := &StructOfTagOne{} //var Struct *StructOfTagOne input := []byte(`{"field3": "100", "field4": "100"}`)