Skip to content

Commit 3055897

Browse files
json: configurable numeric decoding (#137)
* json: stylistic improvements, better code reuse Initial benchmark results show this change to be approximately performance-neutral. * bump tested Go versions to just 1.20 and 1.21 * json: add ParseFlags values UseInt64, UseUint64, UseBigInt * json: use atomic.Pointer
1 parent 6dfc1b0 commit 3055897

File tree

13 files changed

+334
-111
lines changed

13 files changed

+334
-111
lines changed

.github/workflows/benchmark.yml

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,63 @@
1+
---
12
name: Benchmark
23

3-
on:
4-
- pull_request
4+
"on":
5+
- pull_request
56

67
jobs:
78
benchmark:
89
strategy:
910
matrix:
1011
ref:
11-
- master
12-
- ${{ github.sha }}
12+
- master
13+
- ${{ github.sha }}
1314

1415
runs-on: ubuntu-latest
1516

1617
steps:
17-
- name: Steup Go
18-
uses: actions/setup-go@v2
19-
with:
20-
go-version: 1.17
21-
22-
- name: Checkout
23-
uses: actions/checkout@v2
24-
with:
25-
ref: ${{ matrix.ref }}
26-
27-
- name: Run Benchmarks
28-
run: go test -v -run '^$' -bench '(Marshal|Unmarshal)$/codeResponse' -benchmem -benchtime 3s -cpu 1 -count 5 ./json | tee bench.txt
29-
30-
- name: Upload Benchmarks
31-
uses: actions/upload-artifact@v2
32-
with:
33-
name: ${{ matrix.ref }}
34-
path: bench.txt
18+
- name: Setup Go
19+
uses: actions/setup-go@v2
20+
with:
21+
go-version: "1.21"
22+
23+
- name: Checkout
24+
uses: actions/checkout@v2
25+
with:
26+
ref: ${{ matrix.ref }}
27+
28+
- name: Run Benchmarks
29+
# Without 6 iterations, benchstat will claim statistical insignificance.
30+
run: go test -v -run '^$' -bench '(Marshal|Unmarshal)$/codeResponse' -benchmem -benchtime 3s -cpu 1 -count 6 ./json | tee bench.txt
31+
32+
- name: Upload Benchmarks
33+
uses: actions/upload-artifact@v2
34+
with:
35+
name: ${{ matrix.ref }}
36+
path: bench.txt
3537

3638
benchstat:
3739
needs: [benchmark]
3840
runs-on: ubuntu-latest
3941

4042
steps:
41-
- name: Steup Go
42-
uses: actions/setup-go@v2
43-
with:
44-
go-version: 1.17
45-
46-
- name: Setup Benchstat
47-
run: go install golang.org/x/perf/cmd/benchstat@latest
48-
49-
- name: Download Benchmark Results
50-
uses: actions/download-artifact@v2
51-
with:
52-
path: .
53-
54-
- name: Run Benchstat
55-
run: benchstat ./master/bench.txt ./${{ github.sha }}/bench.txt | tee benchstat.txt
56-
57-
- name: Upload Benchstat Results
58-
uses: actions/upload-artifact@v2
59-
with:
60-
name: benchstat
61-
path: benchstat.txt
43+
- name: Steup Go
44+
uses: actions/setup-go@v2
45+
with:
46+
go-version: "1.21"
47+
48+
- name: Setup Benchstat
49+
run: go install golang.org/x/perf/cmd/benchstat@latest
50+
51+
- name: Download Benchmark Results
52+
uses: actions/download-artifact@v2
53+
with:
54+
path: .
55+
56+
- name: Run Benchstat
57+
run: benchstat ./master/bench.txt ./${{ github.sha }}/bench.txt | tee benchstat.txt
58+
59+
- name: Upload Benchstat Results
60+
uses: actions/upload-artifact@v2
61+
with:
62+
name: benchstat
63+
path: benchstat.txt

.github/workflows/test.yml

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1+
---
12
name: Test
23

3-
on:
4-
- pull_request
4+
"on":
5+
- pull_request
56

67
jobs:
78
test:
89
strategy:
910
matrix:
1011
go:
11-
- 1.14
12-
- 1.15
13-
- 1.16
14-
- 1.17
12+
- "1.20"
13+
- "1.21"
1514

1615
runs-on: ubuntu-latest
1716

1817
steps:
19-
- uses: actions/checkout@v2
18+
- uses: actions/checkout@v2
2019

21-
- name: Setup Go ${{ matrix.go }}
22-
uses: actions/setup-go@v2
23-
with:
24-
go-version: ${{ matrix.go }}
20+
- name: Setup Go ${{ matrix.go }}
21+
uses: actions/setup-go@v2
22+
with:
23+
go-version: ${{ matrix.go }}
2524

26-
- name: Download Dependencies
27-
run: go mod download
25+
- name: Download Dependencies
26+
run: go mod download
2827

29-
- name: Run Tests
30-
run: make test
28+
- name: Run Tests
29+
run: make test

Makefile

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.PHONY: test bench-simple clean update-golang-test fuzz fuzz-json
22

3-
golang.version ?= 1.15.2
3+
golang.version ?= 1.21
44
golang.tmp.root := /tmp/golang$(golang.version)
55
golang.tmp.json.root := $(golang.tmp.root)/go-go$(golang.version)/src/encoding/json
66
golang.test.files := $(wildcard json/golang_*_test.go)
@@ -10,7 +10,7 @@ go-fuzz-build := ${GOPATH}/bin/go-fuzz-build
1010
go-fuzz-corpus := ${GOPATH}/src/github.com/dvyukov/go-fuzz-corpus
1111
go-fuzz-dep := ${GOPATH}/src/github.com/dvyukov/go-fuzz/go-fuzz-dep
1212

13-
test: test-ascii test-json test-json-bugs test-json-1.17 test-proto test-iso8601 test-thrift test-purego
13+
test: test-ascii test-json test-json-bugs test-proto test-iso8601 test-thrift test-purego
1414

1515
test-ascii:
1616
go test -cover -race ./ascii
@@ -21,9 +21,6 @@ test-json:
2121
test-json-bugs:
2222
go test -race ./json/bugs/...
2323

24-
test-json-1.17:
25-
go test -cover -race -tags go1.17 ./json
26-
2724
test-proto:
2825
go test -cover -race ./proto
2926

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module github.com/segmentio/encoding
22

3-
go 1.14
3+
go 1.18
44

55
require github.com/segmentio/asm v1.1.3
6+
7+
require golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 // indirect

json/codec.go

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding"
55
"encoding/json"
66
"fmt"
7+
"math/big"
78
"reflect"
89
"sort"
910
"strconv"
@@ -49,19 +50,21 @@ type decodeFunc func(decoder, []byte, unsafe.Pointer) ([]byte, error)
4950
type emptyFunc func(unsafe.Pointer) bool
5051
type sortFunc func([]reflect.Value)
5152

52-
var (
53-
// Eventually consistent cache mapping go types to dynamically generated
54-
// codecs.
55-
//
56-
// Note: using a uintptr as key instead of reflect.Type shaved ~15ns off of
57-
// the ~30ns Marhsal/Unmarshal functions which were dominated by the map
58-
// lookup time for simple types like bool, int, etc..
59-
cache unsafe.Pointer // map[unsafe.Pointer]codec
60-
)
53+
// Eventually consistent cache mapping go types to dynamically generated
54+
// codecs.
55+
//
56+
// Note: using a uintptr as key instead of reflect.Type shaved ~15ns off of
57+
// the ~30ns Marhsal/Unmarshal functions which were dominated by the map
58+
// lookup time for simple types like bool, int, etc..
59+
var cache atomic.Pointer[map[unsafe.Pointer]codec]
6160

6261
func cacheLoad() map[unsafe.Pointer]codec {
63-
p := atomic.LoadPointer(&cache)
64-
return *(*map[unsafe.Pointer]codec)(unsafe.Pointer(&p))
62+
p := cache.Load()
63+
if p == nil {
64+
return nil
65+
}
66+
67+
return *p
6568
}
6669

6770
func cacheStore(typ reflect.Type, cod codec, oldCodecs map[unsafe.Pointer]codec) {
@@ -72,7 +75,7 @@ func cacheStore(typ reflect.Type, cod codec, oldCodecs map[unsafe.Pointer]codec)
7275
newCodecs[t] = c
7376
}
7477

75-
atomic.StorePointer(&cache, *(*unsafe.Pointer)(unsafe.Pointer(&newCodecs)))
78+
cache.Store(&newCodecs)
7679
}
7780

7881
func typeid(t reflect.Type) unsafe.Pointer {
@@ -838,6 +841,7 @@ func constructInlineValueEncodeFunc(encode encodeFunc) encodeFunc {
838841
// compiles down to zero instructions.
839842
// USE CAREFULLY!
840843
// This was copied from the runtime; see issues 23382 and 7921.
844+
//
841845
//go:nosplit
842846
func noescape(p unsafe.Pointer) unsafe.Pointer {
843847
x := uintptr(p)
@@ -1078,6 +1082,7 @@ var (
10781082
float32Type = reflect.TypeOf(float32(0))
10791083
float64Type = reflect.TypeOf(float64(0))
10801084

1085+
bigIntType = reflect.TypeOf(new(big.Int))
10811086
numberType = reflect.TypeOf(json.Number(""))
10821087
stringType = reflect.TypeOf("")
10831088
stringsType = reflect.TypeOf([]string(nil))
@@ -1104,6 +1109,8 @@ var (
11041109
jsonUnmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
11051110
textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
11061111
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
1112+
1113+
bigIntDecoder = constructJSONUnmarshalerDecodeFunc(bigIntType, false)
11071114
)
11081115

11091116
// =============================================================================

0 commit comments

Comments
 (0)