Skip to content

Commit e45a75c

Browse files
committed
perf(gnolang): make print+println much more efficient
This change is the result of seeing print/println being reported in bugs as needing gas metering but on examination, noticed that the code causes a RAM and CPU bloat, so this makes an improvement in all dimensions by invoking: * io.WriteString(m.Output, segments) instead of m.Output.Write([]byte(segments)) * in cases where `!debug`, we don't need to construct the concatenated string delimited by " " and can instead write the Sprint-ed string segments as we encounter them ```shell $ benchstat before_println.txt after_println3.txt name old time/op new time/op delta GnoPrintln-8 1.94ms ± 2% 1.86ms ± 4% -4.02% (p=0.001 n=9+9) name old alloc/op new alloc/op delta GnoPrintln-8 1.92MB ± 0% 1.81MB ± 0% -5.92% (p=0.000 n=10+10) name old allocs/op new allocs/op delta GnoPrintln-8 19.0k ± 0% 16.0k ± 0% -15.76% (p=0.002 n=8+10) ``` Fixes #3951
1 parent 5e017a4 commit e45a75c

File tree

2 files changed

+195
-14
lines changed

2 files changed

+195
-14
lines changed
+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package gnolang
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
9+
"github.com/gnolang/gno/gnovm"
10+
"github.com/gnolang/gno/tm2/pkg/db/memdb"
11+
"github.com/gnolang/gno/tm2/pkg/store/dbadapter"
12+
"github.com/gnolang/gno/tm2/pkg/store/iavl"
13+
stypes "github.com/gnolang/gno/tm2/pkg/store/types"
14+
)
15+
16+
var pSink any = nil
17+
18+
func BenchmarkGnoPrintln(b *testing.B) {
19+
var buf bytes.Buffer
20+
db := memdb.NewMemDB()
21+
baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{})
22+
iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{})
23+
store := NewStore(nil, baseStore, iavlStore)
24+
25+
m := NewMachineWithOptions(MachineOptions{
26+
Output: &buf,
27+
Store: store,
28+
})
29+
30+
program := `package p
31+
func main() {
32+
for i := 0; i < 1000; i++ {
33+
println("abcdeffffffffffffffff1222 11111 11111")
34+
}
35+
}`
36+
m.RunMemPackage(&gnovm.MemPackage{
37+
Name: "p",
38+
Path: "p",
39+
Files: []*gnovm.MemFile{
40+
{Name: "a.gno", Body: program},
41+
},
42+
}, false)
43+
44+
b.ReportAllocs()
45+
b.ResetTimer()
46+
47+
for i := 0; i < b.N; i++ {
48+
buf.Reset()
49+
m.RunStatement(S(Call(Nx("main"))))
50+
pSink = buf.String()
51+
}
52+
53+
if pSink == nil {
54+
b.Fatal("Benchmark did not run!")
55+
}
56+
}
57+
58+
func TestGnoPrintAndPrintln(t *testing.T) {
59+
tests := []struct {
60+
name string
61+
srcArgs string
62+
want string
63+
}{
64+
{
65+
"print with no args",
66+
"print()",
67+
"",
68+
},
69+
{
70+
"print with 1 arg",
71+
`print("1")`,
72+
"1",
73+
},
74+
{
75+
"print with 2 args",
76+
`print("1", 2)`,
77+
"1 2",
78+
},
79+
{
80+
"print with 3 args",
81+
`print("1", 2, "*")`,
82+
"1 2 *",
83+
},
84+
{
85+
"print with own spaces",
86+
`print("1 ", 2, "*")`,
87+
"1 2 *",
88+
},
89+
{
90+
"println with no args",
91+
"println()",
92+
"",
93+
},
94+
{
95+
"print with 1 arg",
96+
`println("1")`,
97+
"1\n",
98+
},
99+
{
100+
"println with 2 args",
101+
`println("1", 2)`,
102+
"1 2\n",
103+
},
104+
{
105+
"println with 3 args",
106+
`println("1", 2, "*")`,
107+
"1 2 *\n",
108+
},
109+
{
110+
"println with own spaces",
111+
`println("1 ", 2, "*")`,
112+
"1 2 *\n",
113+
},
114+
}
115+
116+
for _, tt := range tests {
117+
t.Run(tt.name, func(t *testing.T) {
118+
var buf bytes.Buffer
119+
db := memdb.NewMemDB()
120+
baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{})
121+
iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{})
122+
store := NewStore(nil, baseStore, iavlStore)
123+
124+
m := NewMachineWithOptions(MachineOptions{
125+
Output: &buf,
126+
Store: store,
127+
})
128+
129+
program := `package p
130+
func main() {` + tt.srcArgs + "\n}"
131+
m.RunMemPackage(&gnovm.MemPackage{
132+
Name: "p",
133+
Path: "p",
134+
Files: []*gnovm.MemFile{
135+
{Name: "a.gno", Body: program},
136+
},
137+
}, false)
138+
139+
buf.Reset()
140+
m.RunStatement(S(Call(Nx("main"))))
141+
got := buf.String()
142+
if diff := cmp.Diff(got, tt.want); diff != "" {
143+
t.Fatalf("Mismatched output: got - want +\n%s", diff)
144+
}
145+
})
146+
}
147+
}

gnovm/pkg/gnolang/uverse.go

+48-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package gnolang
22

33
import (
44
"fmt"
5+
"io"
56
"strings"
67

78
bm "github.com/gnolang/gno/gnovm/pkg/benchops"
@@ -718,16 +719,32 @@ func makeUverseNode() {
718719
arg0 := m.LastBlock().GetParams1()
719720
xv := arg0
720721
xvl := xv.TV.GetLength()
721-
ss := make([]string, xvl)
722-
for i := 0; i < xvl; i++ {
723-
ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref()
724-
ss[i] = ev.Sprint(m)
722+
if xvl == 0 {
723+
// Mimick Go in which invoking print() does nothing.
724+
return
725725
}
726-
rs := strings.Join(ss, " ")
727-
if debug {
726+
727+
switch debug {
728+
case true:
729+
ss := make([]string, xvl)
730+
for i := 0; i < xvl; i++ {
731+
ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref()
732+
ss[i] = ev.Sprint(m)
733+
}
734+
rs := strings.Join(ss, " ")
728735
print(rs)
736+
io.WriteString(m.Output, rs)
737+
738+
default:
739+
nMax := xvl - 1
740+
for i := 0; i < xvl; i++ {
741+
ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref()
742+
io.WriteString(m.Output, ev.Sprint(m))
743+
if i < nMax { // Not the last item.
744+
io.WriteString(m.Output, " ")
745+
}
746+
}
729747
}
730-
m.Output.Write([]byte(rs))
731748
},
732749
)
733750
defNative("println",
@@ -739,16 +756,33 @@ func makeUverseNode() {
739756
arg0 := m.LastBlock().GetParams1()
740757
xv := arg0
741758
xvl := xv.TV.GetLength()
742-
ss := make([]string, xvl)
743-
for i := 0; i < xvl; i++ {
744-
ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref()
745-
ss[i] = ev.Sprint(m)
759+
if xvl == 0 {
760+
// Mimick Go in which invoking println() does nothing.
761+
return
746762
}
747-
rs := strings.Join(ss, " ") + "\n"
748-
if debug {
763+
764+
switch debug {
765+
case true:
766+
ss := make([]string, xvl)
767+
for i := 0; i < xvl; i++ {
768+
ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref()
769+
ss[i] = ev.Sprint(m)
770+
}
771+
rs := strings.Join(ss, " ") + "\n"
749772
println("DEBUG/stdout: " + rs)
773+
io.WriteString(m.Output, rs)
774+
775+
default:
776+
nMax := xvl - 1
777+
for i := 0; i < xvl; i++ {
778+
ev := xv.TV.GetPointerAtIndexInt(m.Store, i).Deref()
779+
io.WriteString(m.Output, ev.Sprint(m))
780+
if i < nMax { // Not the last item.
781+
io.WriteString(m.Output, " ")
782+
}
783+
}
784+
io.WriteString(m.Output, "\n")
750785
}
751-
m.Output.Write([]byte(rs))
752786
},
753787
)
754788
defNative("recover",

0 commit comments

Comments
 (0)