Use `strings.Builder` to save some memory
apocelipes opened this issue Β· comments
Currently, the Srender
and Sprint
method of some components uses "+=" to add the result. The string returned by Srender
would be very long, and using "+=" will waste some memory.
strings.Builder
is specially designed to handle such scenarios. strings.Builder
was introduced in Go 1.10, and its doc says "A Builder is used to efficiently build a string".
I did a simple benchmark using Barchart as an example:
func BenchmarkBarchart(b *testing.B) {
bars := []Bar{
{Label: "A", Value: 10},
{Label: "B", Value: 20},
{Label: "C", Value: 30},
{Label: "D", Value: 40},
{Label: "E", Value: 50},
{Label: "F", Value: 40},
{Label: "G", Value: 30},
{Label: "H", Value: 20},
{Label: "I", Value: 10},
}
for i := 0; i < b.N; i++ {
DefaultBarChart.WithBars(bars).Render()
}
}
and this is the result:
$ go test -run='^$' -bench='BenchmarkBarchart' -benchmem -count=10 > old.bench
# change `var ret string` to `var ret strings.Builder`
$ go test -run='^$' -bench='BenchmarkBarchart' -benchmem -count=10 > new.bench
$ benchstat old.bench new.bench
goos: windows
goarch: amd64
pkg: github.com/pterm/pterm
cpu: Intel(R) Core(TM) i5-10200H CPU @ 2.40GHz
β old.bench β new.bench β
β sec/op β sec/op vs base β
Barchart-8 97.84m Β± 7% 94.15m Β± 4% ~ (p=0.123 n=10)
β old.bench β new.bench β
β B/op β B/op vs base β
Barchart-8 5.803Mi Β± 8% 4.179Mi Β± 7% -27.99% (p=0.000 n=10)
β old.bench β new.bench β
β allocs/op β allocs/op vs base β
Barchart-8 9.902k Β± 0% 9.501k Β± 0% -4.05% (p=0.000 n=10)
Using strings.Builder
can save 28% of memory and reduce 4% allocations. There is no obvious change in the speed of Srender
(in fact, the speed can become faster after reducing the number of memory allocations)
The changes of code is also quite simple:
func (p BarChartPrinter) Srender() (string, error) {
- var ret string
+ var ret strings.Builder
...
for i := 0; i <= maxBarHeight; i++ {
for _, barString := range renderedBars {
...
- ret += barLine
+ ret.WriteString(barLine)
}
- ret += "\n"
+ ret.WriteByte('\n')
}
- return ret, nil
+ return ret.String(), nil
}
Code that needs to be modified: Srender
and Sprint
methods that require a lot of string concatenation. All modifications can be seen in the related PR submitted later.
These modifications will not change the existing APIs. It is a simple and harmless refactoring.
I hope you would take the time to review this proposal.