A changed cell only looks through the formulas that read other sheets when some formula names its sheet. Two benchmarks compare an edit read by 5,000 formulas across sheets and on one sheet.
- commit
- 38ce725b97ac0e526429cbad96c47dc606e7d7f4
- parent
- ed235055c7fc430c889523744c203b435a91ac15
- author
- Dami <dami.etoile@gmail.com>
| M | internal/ | 3 | +++ |
| M | internal/ | 15 | ++++++++++++-- |
| M | internal/ | 33 | ++++++++++++++++++++++++++++++ |
@@ -451,6 +451,9 @@ func (w *Workbook) affected(changed []loc) map[loc]int {
451451 }452452 }453453 }454+ if w.crossKeys[sheetKey(s.name)] == 0 {455+ continue // no formula names this sheet456+ }454457 for u := range w.crossUsers {455458 if w.crossReads(u, l) {456459 push(u)@@ -28,9 +28,11 @@ type Workbook struct {
2828 nameUsers map[string]map[loc]struct{}2929 3030 // crossUsers are the formulas that reference a sheet by name. Like31- // range users, they are scanned when a cell changes; cross-sheet32- // formulas are usually few, so this stays cheap.31+ // range users, they are scanned when a cell changes, but only for32+ // cells on sheets some formula names (crossKeys counts references by33+ // sheetKey), so sheets nobody reads by name cost nothing.3334 crossUsers map[loc]struct{}35+ crossKeys map[string]int3436 3537 // Circular is set when the last recalculation found a cycle.3638 Circular bool@@ -66,6 +68,7 @@ func emptyBook() *Workbook {
6668 names: map[string]Name{},6769 nameUsers: map[string]map[loc]struct{}{},6870 crossUsers: map[loc]struct{}{},71+ crossKeys: map[string]int{},6972 }7073 }7174 @@ -430,6 +433,9 @@ func (w *Workbook) index(l loc, c *Cell) {
430433 if len(c.xrefs) > 0 {431434 w.crossUsers[l] = struct{}{}432435 }436+ for _, x := range c.xrefs {437+ w.crossKeys[x.key]++438+ }433439 }434440 435441 func (w *Workbook) unindex(l loc, c *Cell) {@@ -440,6 +446,11 @@ func (w *Workbook) unindex(l loc, c *Cell) {
440446 }441447 }442448 delete(w.crossUsers, l)449+ for _, x := range c.xrefs {450+ if w.crossKeys[x.key]--; w.crossKeys[x.key] <= 0 {451+ delete(w.crossKeys, x.key)452+ }453+ }443454 }444455 445456 // crossList returns the cross-sheet formulas, so callers may change them@@ -2,6 +2,7 @@ package sheet
22 33 import (44 "bytes"5+ "strconv"56 "strings"67 "testing"78 )@@ -448,6 +449,38 @@ func TestDecimalIsWorkbookWide(t *testing.T) {
448449 }449450 }450451 452+// BenchmarkCrossSheetEdit edits a cell that thousands of formulas on453+// another sheet read, the case the cross-sheet index scans for.454+func BenchmarkCrossSheetEdit(b *testing.B) {455+ w := NewBook()456+ one := w.Sheet(0)457+ data, _ := w.AddSheet("Data", 1)458+ for i := range 5000 {459+ a := Addr{Row: i}460+ data.put(a, strconv.Itoa(i))461+ one.put(a, "=Data!A"+strconv.Itoa(i+1)+"*2+SUM(Data!A1:A10)")462+ }463+ w.RecalcAll()464+ b.ResetTimer()465+ for i := range b.N {466+ data.Set(Addr{}, strconv.Itoa(i))467+ }468+}469+470+// BenchmarkSameSheetEdit is BenchmarkCrossSheetEdit on one sheet.471+func BenchmarkSameSheetEdit(b *testing.B) {472+ s := New()473+ for i := range 5000 {474+ s.put(Addr{Row: i}, strconv.Itoa(i))475+ s.put(Addr{Col: 1, Row: i}, "=A"+strconv.Itoa(i+1)+"*2+SUM(A1:A10)")476+ }477+ s.RecalcAll()478+ b.ResetTimer()479+ for i := range b.N {480+ s.Set(Addr{}, strconv.Itoa(i))481+ }482+}483+451484 func TestSplitSheet(t *testing.T) {452485 tests := []struct{ in, sheet, rest string }{453486 {"A1", "", "A1"},