francoispqt / gojay

high performance JSON encoder/decoder with stream API for Golang
MIT License
2.11k stars 112 forks source link

Decoder never releases any memory #79

Closed jogramming closed 5 years ago

jogramming commented 5 years ago

I'm running into a issue where the decoder is never releasing any memory, and just contiously eats up more and more as you decode, with all the mentions of pooling and reusing decoders, i'm assuming this should not be the case?

code to reproduce (it eats up ram pretty fast so i wouldn't run it for more than a couple seconds):


package main

import (
    "bytes"
    "github.com/francoispqt/gojay"
    "log"
    "runtime"
    "time"
)

const encodedData = `{"a": 100, "b": "hello world"}`

type Data struct {
    A int64
    B string
}

// implement gojay.UnmarshalerJSONObject
func (d *Data) UnmarshalJSONObject(dec *gojay.Decoder, key string) error {
    switch key {
    case "a":
        return dec.Int64(&d.A)
    case "b":
        return dec.String(&d.B)
    }

    return nil
}

func (evt *Data) NKeys() int {
    return 0 // refuses to work without this being 0 for some reason
}

func main() {
    go printStats()

    RunWorker()
}

func RunWorker() {
    buf := &bytes.Buffer{}
    decoder := gojay.NewDecoder(buf)
    dst := &Data{}
    for {
        _, err := buf.WriteString(encodedData)
        if err != nil {
            log.Fatal(err)
                        return
        }

        err = decoder.Decode(dst)
        if err != nil {
            log.Fatal(err)
            return
        }

        buf.Reset()
    }
}

func printStats() {
    tc := time.NewTicker(time.Second)

    for {
        var memStats runtime.MemStats
        runtime.ReadMemStats(&memStats)
        log.Printf("Alloc: %5.1fMB, Sys: %5.1fMB", float64(memStats.Alloc)/1000000, float64(memStats.Sys)/1000000)
        <-tc.C
    }
}
jogramming commented 5 years ago

So i see that the BorrowDecoder does in fact reset the cursor (and also allocates a new buffer?)

But i don't need pooling for my use case (have 1 decoder per long lasting connection) and introducing something i dont need into a hot spot isn't something i'm fond of.

francoispqt commented 5 years ago

At the moment, you must borrow a decoder and release it everytime you want to decode something. Maybe we could introduce a way to reset the decoder for those who want to reuse the same decoder multiple time. Like a ˋdec.Reset()` method.

jogramming commented 5 years ago

I ended up forking it and resetting it at the end of each Decode call https://github.com/jonas747/gojay/commit/9081ac11e06c13f278632914f86a5bcf8e3c19a5

Seems to work perfectly for my use case.

francoispqt commented 5 years ago

Ok, I will close the issue but will create a milestone to add an exposed Reset method.

Thanks