From 0fc011a87032e0203eada3116e88a395150554a6 Mon Sep 17 00:00:00 2001 From: Petar Dambovaliev Date: Fri, 3 May 2024 18:01:14 +0200 Subject: [PATCH] perf: machine string (#1994) Make the `String` method on the `Machine` struct faster by preallocating a string builder. [issue](https://github.com/gnolang/gno/issues/1981) --- gnovm/pkg/gnolang/gno_test.go | 25 +++++++ gnovm/pkg/gnolang/machine.go | 126 +++++++++++++++------------------- gnovm/pkg/gnolang/values.go | 2 +- 3 files changed, 83 insertions(+), 70 deletions(-) diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index dca2d0d9757..95c4ec7a84c 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -17,6 +17,31 @@ import ( "github.com/stretchr/testify/require" ) +func setupMachine(b *testing.B, numValues, numStmts, numExprs, numBlocks, numFrames, numExceptions int) *Machine { + b.Helper() + + m := &Machine{ + Ops: make([]Op, 100), + NumOps: 100, + Values: make([]TypedValue, numValues), + NumValues: numValues, + Exprs: make([]Expr, numExprs), + Stmts: make([]Stmt, numStmts), + Blocks: make([]*Block, numBlocks), + Frames: make([]*Frame, numFrames), + Exceptions: make([]Exception, numExceptions), + } + return m +} + +func BenchmarkStringLargeData(b *testing.B) { + m := setupMachine(b, 10000, 5000, 5000, 2000, 3000, 1000) + + for i := 0; i < b.N; i++ { + _ = m.String() + } +} + func TestRunInvalidLabels(t *testing.T) { tests := []struct { code string diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 018fac66e64..69210570c0d 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -2024,117 +2024,105 @@ func (m *Machine) Printf(format string, args ...interface{}) { } func (m *Machine) String() string { - vs := []string{} + // Calculate some reasonable total length to avoid reallocation + // Assuming an average length of 32 characters per string + var ( + vsLength = m.NumValues * 32 + ssLength = len(m.Stmts) * 32 + xsLength = len(m.Exprs) * 32 + bsLength = 1024 + obsLength = len(m.Blocks) * 32 + fsLength = len(m.Frames) * 32 + exceptionsLength = len(m.Exceptions) + + totalLength = vsLength + ssLength + xsLength + bsLength + obsLength + fsLength + exceptionsLength + ) + + var builder strings.Builder + builder.Grow(totalLength) + + builder.WriteString(fmt.Sprintf("Machine:\n CheckTypes: %v\n Op: %v\n Values: (len: %d)\n", m.CheckTypes, m.Ops[:m.NumOps], m.NumValues)) + for i := m.NumValues - 1; i >= 0; i-- { - v := m.Values[i] - vs = append(vs, fmt.Sprintf(" #%d %v", i, v)) + builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Values[i])) } - ss := []string{} - for i := len(m.Stmts) - 1; i >= 0; i-- { - s := m.Stmts[i] - ss = append(ss, fmt.Sprintf(" #%d %v", i, s)) - } - xs := []string{} + for i := len(m.Exprs) - 1; i >= 0; i-- { - x := m.Exprs[i] - xs = append(xs, fmt.Sprintf(" #%d %v", i, x)) + builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Exprs[i])) } - bs := []string{} + + for i := len(m.Stmts) - 1; i >= 0; i-- { + builder.WriteString(fmt.Sprintf(" #%d %v\n", i, m.Stmts[i])) + } + for b := m.LastBlock(); b != nil; { - gen := len(bs)/3 + 1 + gen := builder.Len()/3 + 1 gens := "@" // strings.Repeat("@", gen) + if pv, ok := b.Source.(*PackageNode); ok { // package blocks have too much, so just // print the pkgpath. - bs = append(bs, fmt.Sprintf(" %s(%d) %s", gens, gen, pv.PkgPath)) + builder.WriteString(fmt.Sprintf(" %s(%d) %s\n", gens, gen, pv.PkgPath)) } else { bsi := b.StringIndented(" ") - bs = append(bs, fmt.Sprintf(" %s(%d) %s", gens, gen, bsi)) + builder.WriteString(fmt.Sprintf(" %s(%d) %s\n", gens, gen, bsi)) + if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - bs = append(bs, fmt.Sprintf(" (s vals) %s(%d) %s", gens, gen, - sb.StringIndented(" "))) + builder.WriteString(fmt.Sprintf(" (s vals) %s(%d) %s\n", gens, gen, sb.StringIndented(" "))) + sts := b.GetSource(m.Store).GetStaticBlock().Types - bs = append(bs, fmt.Sprintf(" (s typs) %s(%d) %s", gens, gen, - sts)) + builder.WriteString(fmt.Sprintf(" (s typs) %s(%d) %s\n", gens, gen, sts)) } } - // b = b.Parent.(*Block|RefValue) + + // Update b switch bp := b.Parent.(type) { case nil: b = nil - break case *Block: b = bp case RefValue: - bs = append(bs, fmt.Sprintf(" (block ref %v)", bp.ObjectID)) + builder.WriteString(fmt.Sprintf(" (block ref %v)\n", bp.ObjectID)) b = nil - break default: panic("should not happen") } } - obs := []string{} + for i := len(m.Blocks) - 2; i >= 0; i-- { b := m.Blocks[i] + + if b == nil || b.Source == nil { + continue + } + if _, ok := b.Source.(*PackageNode); ok { break // done, skip *PackageNode. } else { - obs = append(obs, fmt.Sprintf(" #%d %s", i, + builder.WriteString(fmt.Sprintf(" #%d %s", i, b.StringIndented(" "))) if b.Source != nil { sb := b.GetSource(m.Store).GetStaticBlock().GetBlock() - obs = append(obs, fmt.Sprintf(" (static) #%d %s", i, + builder.WriteString(fmt.Sprintf(" (static) #%d %s", i, sb.StringIndented(" "))) } } } - fs := []string{} + for i := len(m.Frames) - 1; i >= 0; i-- { - fr := m.Frames[i] - fs = append(fs, fmt.Sprintf(" #%d %s", i, fr.String())) + builder.WriteString(fmt.Sprintf(" #%d %s\n", i, m.Frames[i])) } - rlmpath := "" + if m.Realm != nil { - rlmpath = m.Realm.Path - } - exceptions := make([]string, len(m.Exceptions)) - for i, ex := range m.Exceptions { - exceptions[i] = ex.Sprint(m) - } - return fmt.Sprintf(`Machine: - CheckTypes: %v - Op: %v - Values: (len: %d) -%s - Exprs: -%s - Stmts: -%s - Blocks: -%s - Blocks (other): -%s - Frames: -%s - Realm: - %s - Exceptions: - %s - %s`, - m.CheckTypes, - m.Ops[:m.NumOps], - m.NumValues, - strings.Join(vs, "\n"), - strings.Join(xs, "\n"), - strings.Join(ss, "\n"), - strings.Join(bs, "\n"), - strings.Join(obs, "\n"), - strings.Join(fs, "\n"), - rlmpath, - m.Exceptions, - strings.Join(exceptions, "\n"), - ) + builder.WriteString(fmt.Sprintf(" Realm:\n %s\n", m.Realm.Path)) + } + + for _, ex := range m.Exceptions { + builder.WriteString(fmt.Sprintf(" %s\n", ex.Sprint(m))) + } + + return builder.String() } //---------------------------------------- diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index ae5ac7fd40b..bc9af8e1a32 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -2284,7 +2284,7 @@ func (b *Block) StringIndented(indent string) string { if len(source) > 32 { source = source[:32] + "..." } - lines := []string{} + lines := make([]string, 0, 3) lines = append(lines, fmt.Sprintf("Block(ID:%v,Addr:%p,Source:%s,Parent:%p)", b.ObjectInfo.ID, b, source, b.Parent)) // XXX Parent may be RefValue{}.