Source file src/encoding/json/bench_test.go

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Large data benchmark.
     6  // The JSON data is a summary of agl's changes in the
     7  // go, webkit, and chromium open source projects.
     8  // We benchmark converting between the JSON form
     9  // and in-memory data structures.
    10  
    11  //go:build !goexperiment.jsonv2
    12  
    13  package json
    14  
    15  import (
    16  	"bytes"
    17  	"fmt"
    18  	"internal/testenv"
    19  	"internal/zstd"
    20  	"io"
    21  	"os"
    22  	"reflect"
    23  	"regexp"
    24  	"runtime"
    25  	"strings"
    26  	"sync"
    27  	"testing"
    28  )
    29  
    30  type codeResponse struct {
    31  	Tree     *codeNode `json:"tree"`
    32  	Username string    `json:"username"`
    33  }
    34  
    35  type codeNode struct {
    36  	Name     string      `json:"name"`
    37  	Kids     []*codeNode `json:"kids"`
    38  	CLWeight float64     `json:"cl_weight"`
    39  	Touches  int         `json:"touches"`
    40  	MinT     int64       `json:"min_t"`
    41  	MaxT     int64       `json:"max_t"`
    42  	MeanT    int64       `json:"mean_t"`
    43  }
    44  
    45  var codeJSON []byte
    46  var codeStruct codeResponse
    47  
    48  func codeInit() {
    49  	f, err := os.Open("internal/jsontest/testdata/golang_source.json.zst")
    50  	if err != nil {
    51  		panic(err)
    52  	}
    53  	defer f.Close()
    54  	gz := zstd.NewReader(f)
    55  	data, err := io.ReadAll(gz)
    56  	if err != nil {
    57  		panic(err)
    58  	}
    59  
    60  	codeJSON = data
    61  
    62  	if err := Unmarshal(codeJSON, &codeStruct); err != nil {
    63  		panic("unmarshal code.json: " + err.Error())
    64  	}
    65  
    66  	if data, err = Marshal(&codeStruct); err != nil {
    67  		panic("marshal code.json: " + err.Error())
    68  	}
    69  
    70  	if !bytes.Equal(data, codeJSON) {
    71  		println("different lengths", len(data), len(codeJSON))
    72  		for i := 0; i < len(data) && i < len(codeJSON); i++ {
    73  			if data[i] != codeJSON[i] {
    74  				println("re-marshal: changed at byte", i)
    75  				println("orig: ", string(codeJSON[i-10:i+10]))
    76  				println("new: ", string(data[i-10:i+10]))
    77  				break
    78  			}
    79  		}
    80  		panic("re-marshal code.json: different result")
    81  	}
    82  }
    83  
    84  func BenchmarkCodeEncoder(b *testing.B) {
    85  	b.ReportAllocs()
    86  	if codeJSON == nil {
    87  		b.StopTimer()
    88  		codeInit()
    89  		b.StartTimer()
    90  	}
    91  	b.RunParallel(func(pb *testing.PB) {
    92  		enc := NewEncoder(io.Discard)
    93  		for pb.Next() {
    94  			if err := enc.Encode(&codeStruct); err != nil {
    95  				b.Fatalf("Encode error: %v", err)
    96  			}
    97  		}
    98  	})
    99  	b.SetBytes(int64(len(codeJSON)))
   100  }
   101  
   102  func BenchmarkCodeEncoderError(b *testing.B) {
   103  	b.ReportAllocs()
   104  	if codeJSON == nil {
   105  		b.StopTimer()
   106  		codeInit()
   107  		b.StartTimer()
   108  	}
   109  
   110  	// Trigger an error in Marshal with cyclic data.
   111  	type Dummy struct {
   112  		Name string
   113  		Next *Dummy
   114  	}
   115  	dummy := Dummy{Name: "Dummy"}
   116  	dummy.Next = &dummy
   117  
   118  	b.RunParallel(func(pb *testing.PB) {
   119  		enc := NewEncoder(io.Discard)
   120  		for pb.Next() {
   121  			if err := enc.Encode(&codeStruct); err != nil {
   122  				b.Fatalf("Encode error: %v", err)
   123  			}
   124  			if _, err := Marshal(dummy); err == nil {
   125  				b.Fatal("Marshal error: got nil, want non-nil")
   126  			}
   127  		}
   128  	})
   129  	b.SetBytes(int64(len(codeJSON)))
   130  }
   131  
   132  func BenchmarkCodeMarshal(b *testing.B) {
   133  	b.ReportAllocs()
   134  	if codeJSON == nil {
   135  		b.StopTimer()
   136  		codeInit()
   137  		b.StartTimer()
   138  	}
   139  	b.RunParallel(func(pb *testing.PB) {
   140  		for pb.Next() {
   141  			if _, err := Marshal(&codeStruct); err != nil {
   142  				b.Fatalf("Marshal error: %v", err)
   143  			}
   144  		}
   145  	})
   146  	b.SetBytes(int64(len(codeJSON)))
   147  }
   148  
   149  func BenchmarkCodeMarshalError(b *testing.B) {
   150  	b.ReportAllocs()
   151  	if codeJSON == nil {
   152  		b.StopTimer()
   153  		codeInit()
   154  		b.StartTimer()
   155  	}
   156  
   157  	// Trigger an error in Marshal with cyclic data.
   158  	type Dummy struct {
   159  		Name string
   160  		Next *Dummy
   161  	}
   162  	dummy := Dummy{Name: "Dummy"}
   163  	dummy.Next = &dummy
   164  
   165  	b.RunParallel(func(pb *testing.PB) {
   166  		for pb.Next() {
   167  			if _, err := Marshal(&codeStruct); err != nil {
   168  				b.Fatalf("Marshal error: %v", err)
   169  			}
   170  			if _, err := Marshal(dummy); err == nil {
   171  				b.Fatal("Marshal error: got nil, want non-nil")
   172  			}
   173  		}
   174  	})
   175  	b.SetBytes(int64(len(codeJSON)))
   176  }
   177  
   178  func benchMarshalBytes(n int) func(*testing.B) {
   179  	sample := []byte("hello world")
   180  	// Use a struct pointer, to avoid an allocation when passing it as an
   181  	// interface parameter to Marshal.
   182  	v := &struct {
   183  		Bytes []byte
   184  	}{
   185  		bytes.Repeat(sample, (n/len(sample))+1)[:n],
   186  	}
   187  	return func(b *testing.B) {
   188  		for i := 0; i < b.N; i++ {
   189  			if _, err := Marshal(v); err != nil {
   190  				b.Fatalf("Marshal error: %v", err)
   191  			}
   192  		}
   193  	}
   194  }
   195  
   196  func benchMarshalBytesError(n int) func(*testing.B) {
   197  	sample := []byte("hello world")
   198  	// Use a struct pointer, to avoid an allocation when passing it as an
   199  	// interface parameter to Marshal.
   200  	v := &struct {
   201  		Bytes []byte
   202  	}{
   203  		bytes.Repeat(sample, (n/len(sample))+1)[:n],
   204  	}
   205  
   206  	// Trigger an error in Marshal with cyclic data.
   207  	type Dummy struct {
   208  		Name string
   209  		Next *Dummy
   210  	}
   211  	dummy := Dummy{Name: "Dummy"}
   212  	dummy.Next = &dummy
   213  
   214  	return func(b *testing.B) {
   215  		for i := 0; i < b.N; i++ {
   216  			if _, err := Marshal(v); err != nil {
   217  				b.Fatalf("Marshal error: %v", err)
   218  			}
   219  			if _, err := Marshal(dummy); err == nil {
   220  				b.Fatal("Marshal error: got nil, want non-nil")
   221  			}
   222  		}
   223  	}
   224  }
   225  
   226  func BenchmarkMarshalBytes(b *testing.B) {
   227  	b.ReportAllocs()
   228  	// 32 fits within encodeState.scratch.
   229  	b.Run("32", benchMarshalBytes(32))
   230  	// 256 doesn't fit in encodeState.scratch, but is small enough to
   231  	// allocate and avoid the slower base64.NewEncoder.
   232  	b.Run("256", benchMarshalBytes(256))
   233  	// 4096 is large enough that we want to avoid allocating for it.
   234  	b.Run("4096", benchMarshalBytes(4096))
   235  }
   236  
   237  func BenchmarkMarshalBytesError(b *testing.B) {
   238  	b.ReportAllocs()
   239  	// 32 fits within encodeState.scratch.
   240  	b.Run("32", benchMarshalBytesError(32))
   241  	// 256 doesn't fit in encodeState.scratch, but is small enough to
   242  	// allocate and avoid the slower base64.NewEncoder.
   243  	b.Run("256", benchMarshalBytesError(256))
   244  	// 4096 is large enough that we want to avoid allocating for it.
   245  	b.Run("4096", benchMarshalBytesError(4096))
   246  }
   247  
   248  func BenchmarkMarshalMap(b *testing.B) {
   249  	b.ReportAllocs()
   250  	m := map[string]int{
   251  		"key3": 3,
   252  		"key2": 2,
   253  		"key1": 1,
   254  	}
   255  	b.RunParallel(func(pb *testing.PB) {
   256  		for pb.Next() {
   257  			if _, err := Marshal(m); err != nil {
   258  				b.Fatal("Marshal:", err)
   259  			}
   260  		}
   261  	})
   262  }
   263  
   264  func BenchmarkCodeDecoder(b *testing.B) {
   265  	b.ReportAllocs()
   266  	if codeJSON == nil {
   267  		b.StopTimer()
   268  		codeInit()
   269  		b.StartTimer()
   270  	}
   271  	b.RunParallel(func(pb *testing.PB) {
   272  		var buf bytes.Buffer
   273  		dec := NewDecoder(&buf)
   274  		var r codeResponse
   275  		for pb.Next() {
   276  			buf.Write(codeJSON)
   277  			// hide EOF
   278  			buf.WriteByte('\n')
   279  			buf.WriteByte('\n')
   280  			buf.WriteByte('\n')
   281  			if err := dec.Decode(&r); err != nil {
   282  				b.Fatalf("Decode error: %v", err)
   283  			}
   284  		}
   285  	})
   286  	b.SetBytes(int64(len(codeJSON)))
   287  }
   288  
   289  func BenchmarkUnicodeDecoder(b *testing.B) {
   290  	b.ReportAllocs()
   291  	j := []byte(`"\uD83D\uDE01"`)
   292  	b.SetBytes(int64(len(j)))
   293  	r := bytes.NewReader(j)
   294  	dec := NewDecoder(r)
   295  	var out string
   296  	b.ResetTimer()
   297  	for i := 0; i < b.N; i++ {
   298  		if err := dec.Decode(&out); err != nil {
   299  			b.Fatalf("Decode error: %v", err)
   300  		}
   301  		r.Seek(0, 0)
   302  	}
   303  }
   304  
   305  func BenchmarkDecoderStream(b *testing.B) {
   306  	b.ReportAllocs()
   307  	b.StopTimer()
   308  	var buf bytes.Buffer
   309  	dec := NewDecoder(&buf)
   310  	buf.WriteString(`"` + strings.Repeat("x", 1000000) + `"` + "\n\n\n")
   311  	var x any
   312  	if err := dec.Decode(&x); err != nil {
   313  		b.Fatalf("Decode error: %v", err)
   314  	}
   315  	ones := strings.Repeat(" 1\n", 300000) + "\n\n\n"
   316  	b.StartTimer()
   317  	for i := 0; i < b.N; i++ {
   318  		if i%300000 == 0 {
   319  			buf.WriteString(ones)
   320  		}
   321  		x = nil
   322  		switch err := dec.Decode(&x); {
   323  		case err != nil:
   324  			b.Fatalf("Decode error: %v", err)
   325  		case x != 1.0:
   326  			b.Fatalf("Decode: got %v want 1.0", i)
   327  		}
   328  	}
   329  }
   330  
   331  func BenchmarkCodeUnmarshal(b *testing.B) {
   332  	b.ReportAllocs()
   333  	if codeJSON == nil {
   334  		b.StopTimer()
   335  		codeInit()
   336  		b.StartTimer()
   337  	}
   338  	b.RunParallel(func(pb *testing.PB) {
   339  		for pb.Next() {
   340  			var r codeResponse
   341  			if err := Unmarshal(codeJSON, &r); err != nil {
   342  				b.Fatalf("Unmarshal error: %v", err)
   343  			}
   344  		}
   345  	})
   346  	b.SetBytes(int64(len(codeJSON)))
   347  }
   348  
   349  func BenchmarkCodeUnmarshalReuse(b *testing.B) {
   350  	b.ReportAllocs()
   351  	if codeJSON == nil {
   352  		b.StopTimer()
   353  		codeInit()
   354  		b.StartTimer()
   355  	}
   356  	b.RunParallel(func(pb *testing.PB) {
   357  		var r codeResponse
   358  		for pb.Next() {
   359  			if err := Unmarshal(codeJSON, &r); err != nil {
   360  				b.Fatalf("Unmarshal error: %v", err)
   361  			}
   362  		}
   363  	})
   364  	b.SetBytes(int64(len(codeJSON)))
   365  }
   366  
   367  func BenchmarkUnmarshalString(b *testing.B) {
   368  	b.ReportAllocs()
   369  	data := []byte(`"hello, world"`)
   370  	b.RunParallel(func(pb *testing.PB) {
   371  		var s string
   372  		for pb.Next() {
   373  			if err := Unmarshal(data, &s); err != nil {
   374  				b.Fatalf("Unmarshal error: %v", err)
   375  			}
   376  		}
   377  	})
   378  }
   379  
   380  func BenchmarkUnmarshalFloat64(b *testing.B) {
   381  	b.ReportAllocs()
   382  	data := []byte(`3.14`)
   383  	b.RunParallel(func(pb *testing.PB) {
   384  		var f float64
   385  		for pb.Next() {
   386  			if err := Unmarshal(data, &f); err != nil {
   387  				b.Fatalf("Unmarshal error: %v", err)
   388  			}
   389  		}
   390  	})
   391  }
   392  
   393  func BenchmarkUnmarshalInt64(b *testing.B) {
   394  	b.ReportAllocs()
   395  	data := []byte(`3`)
   396  	b.RunParallel(func(pb *testing.PB) {
   397  		var x int64
   398  		for pb.Next() {
   399  			if err := Unmarshal(data, &x); err != nil {
   400  				b.Fatalf("Unmarshal error: %v", err)
   401  			}
   402  		}
   403  	})
   404  }
   405  
   406  func BenchmarkUnmarshalMap(b *testing.B) {
   407  	b.ReportAllocs()
   408  	data := []byte(`{"key1":"value1","key2":"value2","key3":"value3"}`)
   409  	b.RunParallel(func(pb *testing.PB) {
   410  		x := make(map[string]string, 3)
   411  		for pb.Next() {
   412  			if err := Unmarshal(data, &x); err != nil {
   413  				b.Fatalf("Unmarshal error: %v", err)
   414  			}
   415  		}
   416  	})
   417  }
   418  
   419  func BenchmarkIssue10335(b *testing.B) {
   420  	b.ReportAllocs()
   421  	j := []byte(`{"a":{ }}`)
   422  	b.RunParallel(func(pb *testing.PB) {
   423  		var s struct{}
   424  		for pb.Next() {
   425  			if err := Unmarshal(j, &s); err != nil {
   426  				b.Fatalf("Unmarshal error: %v", err)
   427  			}
   428  		}
   429  	})
   430  }
   431  
   432  func BenchmarkIssue34127(b *testing.B) {
   433  	b.ReportAllocs()
   434  	j := struct {
   435  		Bar string `json:"bar,string"`
   436  	}{
   437  		Bar: `foobar`,
   438  	}
   439  	b.RunParallel(func(pb *testing.PB) {
   440  		for pb.Next() {
   441  			if _, err := Marshal(&j); err != nil {
   442  				b.Fatalf("Marshal error: %v", err)
   443  			}
   444  		}
   445  	})
   446  }
   447  
   448  func BenchmarkUnmapped(b *testing.B) {
   449  	b.ReportAllocs()
   450  	j := []byte(`{"s": "hello", "y": 2, "o": {"x": 0}, "a": [1, 99, {"x": 1}]}`)
   451  	b.RunParallel(func(pb *testing.PB) {
   452  		var s struct{}
   453  		for pb.Next() {
   454  			if err := Unmarshal(j, &s); err != nil {
   455  				b.Fatalf("Unmarshal error: %v", err)
   456  			}
   457  		}
   458  	})
   459  }
   460  
   461  func BenchmarkTypeFieldsCache(b *testing.B) {
   462  	b.ReportAllocs()
   463  	var maxTypes int = 1e6
   464  	if testenv.Builder() != "" {
   465  		maxTypes = 1e3 // restrict cache sizes on builders
   466  	}
   467  
   468  	// Dynamically generate many new types.
   469  	types := make([]reflect.Type, maxTypes)
   470  	fs := []reflect.StructField{{
   471  		Type:  reflect.TypeFor[string](),
   472  		Index: []int{0},
   473  	}}
   474  	for i := range types {
   475  		fs[0].Name = fmt.Sprintf("TypeFieldsCache%d", i)
   476  		types[i] = reflect.StructOf(fs)
   477  	}
   478  
   479  	// clearClear clears the cache. Other JSON operations, must not be running.
   480  	clearCache := func() {
   481  		fieldCache = sync.Map{}
   482  	}
   483  
   484  	// MissTypes tests the performance of repeated cache misses.
   485  	// This measures the time to rebuild a cache of size nt.
   486  	for nt := 1; nt <= maxTypes; nt *= 10 {
   487  		ts := types[:nt]
   488  		b.Run(fmt.Sprintf("MissTypes%d", nt), func(b *testing.B) {
   489  			nc := runtime.GOMAXPROCS(0)
   490  			for i := 0; i < b.N; i++ {
   491  				clearCache()
   492  				var wg sync.WaitGroup
   493  				for j := 0; j < nc; j++ {
   494  					wg.Add(1)
   495  					go func(j int) {
   496  						for _, t := range ts[(j*len(ts))/nc : ((j+1)*len(ts))/nc] {
   497  							cachedTypeFields(t)
   498  						}
   499  						wg.Done()
   500  					}(j)
   501  				}
   502  				wg.Wait()
   503  			}
   504  		})
   505  	}
   506  
   507  	// HitTypes tests the performance of repeated cache hits.
   508  	// This measures the average time of each cache lookup.
   509  	for nt := 1; nt <= maxTypes; nt *= 10 {
   510  		// Pre-warm a cache of size nt.
   511  		clearCache()
   512  		for _, t := range types[:nt] {
   513  			cachedTypeFields(t)
   514  		}
   515  		b.Run(fmt.Sprintf("HitTypes%d", nt), func(b *testing.B) {
   516  			b.RunParallel(func(pb *testing.PB) {
   517  				for pb.Next() {
   518  					cachedTypeFields(types[0])
   519  				}
   520  			})
   521  		})
   522  	}
   523  }
   524  
   525  func BenchmarkEncodeMarshaler(b *testing.B) {
   526  	b.ReportAllocs()
   527  
   528  	m := struct {
   529  		A int
   530  		B RawMessage
   531  	}{}
   532  
   533  	b.RunParallel(func(pb *testing.PB) {
   534  		enc := NewEncoder(io.Discard)
   535  
   536  		for pb.Next() {
   537  			if err := enc.Encode(&m); err != nil {
   538  				b.Fatalf("Encode error: %v", err)
   539  			}
   540  		}
   541  	})
   542  }
   543  
   544  func BenchmarkEncoderEncode(b *testing.B) {
   545  	b.ReportAllocs()
   546  	type T struct {
   547  		X, Y string
   548  	}
   549  	v := &T{"foo", "bar"}
   550  	b.RunParallel(func(pb *testing.PB) {
   551  		for pb.Next() {
   552  			if err := NewEncoder(io.Discard).Encode(v); err != nil {
   553  				b.Fatalf("Encode error: %v", err)
   554  			}
   555  		}
   556  	})
   557  }
   558  
   559  func BenchmarkNumberIsValid(b *testing.B) {
   560  	s := "-61657.61667E+61673"
   561  	for i := 0; i < b.N; i++ {
   562  		isValidNumber(s)
   563  	}
   564  }
   565  
   566  func BenchmarkNumberIsValidRegexp(b *testing.B) {
   567  	var jsonNumberRegexp = regexp.MustCompile(`^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$`)
   568  	s := "-61657.61667E+61673"
   569  	for i := 0; i < b.N; i++ {
   570  		jsonNumberRegexp.MatchString(s)
   571  	}
   572  }
   573  
   574  func BenchmarkUnmarshalNumber(b *testing.B) {
   575  	b.ReportAllocs()
   576  	data := []byte(`"-61657.61667E+61673"`)
   577  	var number Number
   578  	for i := 0; i < b.N; i++ {
   579  		if err := Unmarshal(data, &number); err != nil {
   580  			b.Fatal("Unmarshal:", err)
   581  		}
   582  	}
   583  }
   584  

View as plain text