cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

Stress: clearing a full max-cells sheet, and the peak heap of open and save

Dami committed 0aa5dca
BenchmarkClearMax clears 1M x 10 numbers as one step and undoes it, and
reports the heap the step holds with the budget's estimate. Open and Save
report the most heap in use while they run; Save writes to a counter
rather than a buffer, so the file's bytes don't count.
commit
0aa5dca6bc484aa9aea562f34b573c14d4822b4b
parent
6ef84602a33593c2ab1a1d5e28e83b19bfb33912
author
Dami <dami.etoile@gmail.com>
1 file changed, +81 -13
Minternal/sheet/stress_test.go94++++++++++++++++++++++++++----
M internal/sheet/stress_test.go +81 -13
@@ -9,8 +9,11 @@ import (
99 "bytes"1010 "fmt"1111 "runtime"12+ "runtime/metrics"1213 "sync"14+ "sync/atomic"1315 "testing"16+ "time"1417 1518 "github.com/FelineStateMachine/012/internal/sheet"1619 "github.com/FelineStateMachine/012/internal/stress"
@@ -82,6 +85,26 @@ func BenchmarkBigUndo(b *testing.B) {
8285 }8386 }8487 88+// BenchmarkClearMax clears all of a full max-cells sheet of numbers (1M89+// x 10) as one step and undoes it, then reports the heap such a step90+// holds (what forgetting it frees) and the budget's estimate of it.91+func BenchmarkClearMax(b *testing.B) {92+ s := stress.Dense(stress.MaxRows, stress.MaxCols)93+ all := sheet.NewRect(sheet.Addr{}, sheet.Addr{Col: stress.MaxCols - 1, Row: stress.MaxRows - 1})94+ s.ClearHistory()95+ for b.Loop() {96+ s.EraseRange(all)97+ s.Undo()98+ s.ClearHistory()99+ }100+ s.EraseRange(all)101+ est := s.Book().HistoryBytes()102+ held := heap()103+ s.ClearHistory()104+ b.ReportMetric(float64(held-heap())/(1<<20), "MB-held")105+ b.ReportMetric(float64(est)/(1<<20), "MB-estimate")106+}107+85108 // BenchmarkHistoryFull makes MaxUndo whole-column edits, the most the86109 // history keeps, and reports the heap they hold.87110 func BenchmarkHistoryFull(b *testing.B) {
@@ -200,24 +223,29 @@ func BenchmarkFillSeries(b *testing.B) {
200223 }201224 }202225 203-// BenchmarkSave writes the native .012 file and reports its size.226+// BenchmarkSave writes the native .012 file and reports its size and227+// the most heap in use while writing it, above what the sheet holds.204228 func BenchmarkSave(b *testing.B) {205229 for _, sh := range saveShapes() {206230 b.Run(sh.name, func(b *testing.B) {207231 s := sh.build()208- var buf bytes.Buffer209- for b.Loop() {210- buf.Reset()211- if err := s.Write(&buf); err != nil {212- b.Fatal(err)232+ var out counter233+ peak := peakHeap(func() {234+ for b.Loop() {235+ out = 0236+ if err := s.Write(&out); err != nil {237+ b.Fatal(err)238+ }213239 }214- }215- b.ReportMetric(float64(buf.Len())/(1<<20), "MB-file")240+ })241+ b.ReportMetric(float64(out)/(1<<20), "MB-file")242+ b.ReportMetric(float64(peak)/(1<<20), "MB-peak")216243 })217244 }218245 }219246 220-// BenchmarkOpen reads the native .012 file back.247+// BenchmarkOpen reads the native .012 file back, and reports the most248+// heap in use while reading it, the sheet read included.221249 func BenchmarkOpen(b *testing.B) {222250 for _, sh := range saveShapes() {223251 b.Run(sh.name, func(b *testing.B) {
@@ -225,15 +253,55 @@ func BenchmarkOpen(b *testing.B) {
225253 if err := sh.build().Write(&buf); err != nil {226254 b.Fatal(err)227255 }228- for b.Loop() {229- if _, err := sheet.Read(bytes.NewReader(buf.Bytes())); err != nil {230- b.Fatal(err)256+ peak := peakHeap(func() {257+ for b.Loop() {258+ if _, err := sheet.Read(bytes.NewReader(buf.Bytes())); err != nil {259+ b.Fatal(err)260+ }231261 }232- }262+ })263+ b.ReportMetric(float64(peak)/(1<<20), "MB-peak")233264 })234265 }235266 }236267 268+// counter is a writer that keeps only the count of bytes written.269+type counter int64270+271+func (c *counter) Write(p []byte) (int, error) {272+ *c += counter(len(p))273+ return len(p), nil274+}275+276+// peakHeap runs fn and returns the most heap in use meanwhile above what277+// was live before, sampled every millisecond.278+func peakHeap(fn func()) uint64 {279+ base := heap()280+ sample := []metrics.Sample{{Name: "/memory/classes/heap/objects:bytes"}}281+ var peak atomic.Uint64282+ done := make(chan struct{})283+ var wg sync.WaitGroup284+ wg.Go(func() {285+ t := time.NewTicker(time.Millisecond)286+ defer t.Stop()287+ for {288+ metrics.Read(sample)289+ if v := sample[0].Value.Uint64(); v > peak.Load() {290+ peak.Store(v)291+ }292+ select {293+ case <-done:294+ return295+ case <-t.C:296+ }297+ }298+ })299+ fn()300+ close(done)301+ wg.Wait()302+ return peak.Load() - min(base, peak.Load())303+}304+237305 type namedBuild struct {238306 name string239307 build func() *sheet.Sheet