The status line computes Sum, Avg and Count of the selection on every frame. Over 8192x256 numbers that was 2 million map lookups, 90 ms a frame, so with everything selected each keystroke took 90 ms. Iterating the cell map costs a quarter of a lookup per cell, so ranges over a quarter of the sheet's cells now iterate; and the result is kept until a cell or value changes (a version counter bumped by place and evaluate). Keystroke/select-data/dense-8192x256/80x24 91 ms -> 1.8 ms (steady 0.2 ms) Keystroke/extend-data/dense-8192x256/80x24 91 ms -> 29 ms
- commit
- 0f86bf4d0d5f5b48b53e69c670a38a6da60c4c0f
- parent
- c2188bfb5c213c27788046568ff5a3e1d2b3a9d0
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 32 | +++++++++++++++++++++++-------- |
| M | internal/ | 14 | +++++++++++++ |
| M | internal/ | 7 | +++++++ |
@@ -62,9 +62,24 @@ type Stats struct {
6262 Count, Nums int6363 }6464 65-// RangeStats computes Stats over r, visiting whichever is smaller: the66-// cells in r or the non-blank cells in the sheet.65+// statsCache is the last RangeStats result: the status line asks for the66+// selection's statistics on every frame, and over a whole sheet of two67+// million cells they take tens of milliseconds.68+type statsCache struct {69+ r Rect70+ version uint6471+ st Stats72+ ok bool73+}74+75+// RangeStats computes Stats over r, visiting whichever is cheaper: the76+// cells in r one lookup at a time, or every stored cell (iterating the77+// map costs about a quarter of a lookup per cell). The result is kept78+// until a cell or value changes.6779 func (s *Sheet) RangeStats(r Rect) Stats {80+ if c := s.stats; c.ok && c.r == r && c.version == s.version {81+ return c.st82+ }6883 var st Stats6984 add := func(c *Cell) {7085 st.Count++@@ -74,7 +89,7 @@ func (s *Sheet) RangeStats(r Rect) Stats {
7489 }7590 }7691 area := (r.To.Col - r.From.Col + 1) * (r.To.Row - r.From.Row + 1)77- if area <= len(s.cells) {92+ if area <= len(s.cells)/4 {7893 for row := r.From.Row; row <= r.To.Row; row++ {7994 for col := r.From.Col; col <= r.To.Col; col++ {8095 if c := s.cells[Addr{Col: col, Row: row}]; !c.Blank() {@@ -82,12 +97,13 @@ func (s *Sheet) RangeStats(r Rect) Stats {
8297 }8398 }8499 }85- return st86- }87- for a, c := range s.cells {88- if r.Contains(a) && !c.Blank() {89- add(c)100+ } else {101+ for a, c := range s.cells {102+ if r.Contains(a) && !c.Blank() {103+ add(c)104+ }90105 }91106 }107+ s.stats = statsCache{r: r, version: s.version, st: st, ok: true}92108 return st93109 }@@ -47,4 +47,18 @@ func TestUsedRangeAndStats(t *testing.T) {
4747 if got := s.RangeStats(NewRect(at("A1"), at("D8192"))); got != want {4848 t.Errorf("RangeStats large = %+v", got)4949 }50+ // The cached result follows edits, recalculation and undo.51+ r := NewRect(at("A1"), at("D3"))52+ s.Set(at("C1"), "=B2*2")53+ if got := s.RangeStats(r); got != (Stats{Sum: 62, Count: 4, Nums: 3}) {54+ t.Errorf("after an edit = %+v", got)55+ }56+ s.Set(at("B2"), "1")57+ if got := s.RangeStats(r); got != (Stats{Sum: 35, Count: 4, Nums: 3}) {58+ t.Errorf("after a recalc = %+v", got)59+ }60+ s.Undo()61+ if got := s.RangeStats(r); got != (Stats{Sum: 62, Count: 4, Nums: 3}) {62+ t.Errorf("after undo = %+v", got)63+ }5064 }@@ -67,6 +67,11 @@ type Sheet struct {
6767 // Circular is set when the last recalculation found a cycle.6868 Circular bool6969 70+ // version counts changes to cells and their values, so what is derived71+ // from them can be cached; see RangeStats.72+ version uint6473+ stats statsCache74+7075 charts []Chart // floating charts, bottom first; see chart.go7176 7277 hist history // undo and redo, see history.go@@ -280,6 +285,7 @@ func (c *Cell) setExpr(n Node) {
280285 // keeping the dependency indexes current. Every cell mutation goes through281286 // here.282287 func (s *Sheet) place(a Addr, c *Cell) {288+ s.version++283289 s.record(a)284290 s.unlink(a)285291 if c == nil {@@ -435,6 +441,7 @@ func (s *Sheet) affected(changed []Addr) map[Addr]int {
435441 // cell that is reached again while it is still being evaluated is part of436442 // a cycle and becomes ERR.437443 func (s *Sheet) evaluate(state map[Addr]int) {444+ s.version++438445 s.Circular = false439446 s.hidden.valid = false // values may have changed what the filter hides440447 var compute func(Addr) Value