golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
121.8k stars 17.42k forks source link

runtime: memory and performance degradation #12640

Open dvyukov opened 8 years ago

dvyukov commented 8 years ago

Below is the program, I am running it with 1.4 and current tip. There are significant regressions with binary size, execution time and memory consumption. Binary size on 1.4 3581368, binary size on tip is 4096280. Below are results of running it with TIME="%e %M" time:

1.4
4.04 3035492
4.38 3035496
4.66 3035500
4.51 3035500
4.42 3035504
4.34 3035500
4.20 3035496
3.87 3035496
4.07 3035496
4.15 3035504
4.28 3035492

tip
4.93 3009044
5.30 4910668
5.49 2978652
5.05 3929244
5.86 2980032
5.91 3929368
5.24 2980052
5.26 3929196
5.64 2980976
5.60 2979944
5.15 3929228
5.36 3929224
4.97 2980096
6.36 3929472
4.77 3929172

1.4 reliably consumes 3GB, while 1.5 can consume 3GB or 4GB or 5GB. There also seems to be a performance regression of about 30%.

Memory consumption instability and variance seems to be the most troublesome. tip should not consume significantly more than 1.4.

package gob

import (
    "bytes"
    "encoding/gob"
    "fmt"
    "io"
    "reflect"
    "testing"

    "github.com/dvyukov/go-fuzz/examples/fuzz"
)

type X struct {
    A int
    B string
    C float64
    D []byte
    E interface{}
    F complex128
    G []interface{}
    H *int
    I **int
    J *X
    K map[string]int
}

func init() {
    gob.Register(X{})
}

func TestT(t *testing.T) {
    data :=     "#\xff\x99\x03\x01\x01\x03RT\x19\x01\xff\x9a\x00\x01\xfb\x00\aA\x01" +
    "\x04\x00\x01\x01B\x01\f\x00\x01\x01C\x01\b\x00\x00\x00\x16\xff\x9a\x01" +
    "\"\x01\x05hello\x01\u007f\xff\xff\xff\xf0\xf9!\t@\x00"

    Fuzz([]byte(data))
}

func Fuzz(data []byte) int {
    score := 0
    for _, ctor := range []func() interface{}{
        func() interface{} { return nil },
        func() interface{} { return new(int) },
        func() interface{} { return new(string) },
        func() interface{} { return new(float64) },
        func() interface{} { return new([]byte) },
        func() interface{} { return new(interface{}) },
        func() interface{} { return new(complex128) },
        func() interface{} { m := make(map[int]int); return &m },
        func() interface{} { m := make(map[string]interface{}); return &m },
        func() interface{} { return new(X) },
    } {
        v := ctor()
        dec := gob.NewDecoder(bytes.NewReader(data))
        if dec.Decode(v) != nil {
            continue
        }
        dec.Decode(ctor())
        score = 1
        if ctor() == nil {
            continue
        }
        b1 := new(bytes.Buffer)
        if err := gob.NewEncoder(b1).Encode(v); err != nil {
            panic(err)
        }
        v1 := reflect.ValueOf(ctor())
        err := gob.NewDecoder(bytes.NewReader(data)).DecodeValue(v1)
        if err != nil {
            panic(err)
        }
        if !fuzz.DeepEqual(v, v1.Interface()) {
            fmt.Printf("v0: %#v\n", reflect.ValueOf(v).Elem().Interface())
            fmt.Printf("v1: %#v\n", v1.Elem().Interface())
            panic(fmt.Sprintf("values not equal %T", v))
        }
        b2 := new(bytes.Buffer)
        err = gob.NewEncoder(b2).EncodeValue(v1)
        if err != nil {
            panic(err)
        }
        v2 := ctor()
        dec1 := gob.NewDecoder(b1)
        if err := dec1.Decode(v2); err != nil {
            panic(err)
        }
        if err := dec1.Decode(ctor()); err != io.EOF {
            panic(err)
        }
        if vv, ok := v.(*X); ok {
            fix(vv)
        }
        if !fuzz.DeepEqual(v, v2) {
            fmt.Printf("v0: %#v\n", reflect.ValueOf(v).Elem().Interface())
            fmt.Printf("v2: %#v\n", reflect.ValueOf(v2).Elem().Interface())
            panic(fmt.Sprintf("values not equal 2 %T", v))
        }
    }
    return score
}

func fix(vv *X) {
    // See https://github.com/golang/go/issues/11119
    if vv.I != nil && (*vv.I == nil || **vv.I == 0) {
        // If input contains "I:42 I:null", then I will be in this weird state.
        // It is effectively nil, but DeepEqual does not handle such case.
        vv.I = nil
    }
    if vv.H != nil && *vv.H == 0 {
        vv.H = nil
    }
    if vv.J != nil {
        fix(vv.J)
    }
}

go version devel +a1aafdb Tue Sep 15 16:12:59 2015 +0000 linux/amd64

dvyukov commented 8 years ago

@rsc @aclements @RLH @randall77 @dr2chase

rsc commented 8 years ago

We're not going to get to this for Go 1.6.

aclements commented 8 years ago

The memory instability appears to be fixed at tip. There's still a performance regression and the binaries are still bigger.

1.4
4.53 3036356
4.55 3036348
4.66 3036348
4.50 3036348
4.49 3036348

tip (bea9ae2 linux/amd64)
7.76 2947704
8.76 2947704
7.93 2947700
8.43 2947748
7.32 2947688
nishantroy commented 6 years ago

@dvyukov can you still repro this issue with 1.10?

dvyukov commented 6 years ago

I don't have time right now. But anybody is free to try, there is a repro.

agnivade commented 5 years ago

Results with 1.13beta1 (linux/amd64)-

for i in {1..10}; do TIME="%e %M" time go1.13beta1 test -run=TestT; done

3.94 3930436
3.72 1967292
3.12 3930452
3.05 3930480
3.73 2948844
3.17 3009676
3.05 3930480
3.06 3930528
3.70 1967368
3.72 2949060

Performance seems to be fixed but memory usage seems to be jumping around.

@dvyukov @aclements - How do we want to proceed with this ?