go-json icon indicating copy to clipboard operation
go-json copied to clipboard

High Mem & GC when compiling large cyclic data structure

Open kaijietti opened this issue 7 months ago • 0 comments

go.mod:

module gojsontest

go 1.21

require (
	github.com/goccy/go-json v0.10.3
	github.com/stripe/stripe-go/v79 v79.2.0
)

main.go:

package main

import (
	"fmt"
	"log"
	"net/http"
	_ "net/http/pprof"

	"github.com/goccy/go-json"
	"github.com/stripe/stripe-go/v79"
)

func main() {
	go func() {
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	bytes, err := json.Marshal(&stripe.CheckoutSession{})
	if err != nil {
		panic(err)
	}
	fmt.Println(string(bytes))
}

The above code stuck in func (c *Compiler) compile(typeptr uintptr) phase. Plenty of memory was allocated and then GC contributed most of the CPU time.

A pprof sample is here. pprof.samples.cpu.001.pb.gz

And I used the following script to check how many cycle definition:

package main

import (
	"fmt"
	"reflect"

	"github.com/stripe/stripe-go/v79"
)

func findTypeCycle(t reflect.Type, visited map[reflect.Type]bool, cycleCount *int) {
	if visited[t] {
		*cycleCount++
		return
	}

	visited[t] = true
	defer delete(visited, t)

	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fieldType := field.Type

		if fieldType.Kind() == reflect.Ptr {
			fieldType = fieldType.Elem()
		}

		if fieldType.Kind() == reflect.Struct {
			findTypeCycle(fieldType, visited, cycleCount)
		}
	}
}

func main() {
	cycleCount := 0
	findTypeCycle(reflect.TypeOf(stripe.CheckoutSession{}), make(map[reflect.Type]bool), &cycleCount)
	fmt.Printf("Total number of cycles detected: %d\n", cycleCount)
}

And the output is:

Total number of cycles detected: 1083720

kaijietti avatar Jul 11 '24 13:07 kaijietti