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

View as plain text