Commit c66dad95 by Ben Clayton

Regres: Add invertCommon optimization.

invertCommon looks for tree nodes with the majority of the child nodes with the same spans. This span is promoted up to the parent, and the children have the span inverted. This causes the span IDs to flip (inclusive / exlusive) each time they're seen while decending the tree. This adds a bit of complexity to the parsing of the data, but the file size reduction is significant. Bug: b/152192800 Change-Id: I5ec294d42004d936004c27ef15bf94a7143372ba Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/43311 Kokoro-Presubmit: kokoro <noreply+kokoro@google.com> Reviewed-by: 's avatarNicolas Capens <nicolascapens@google.com> Tested-by: 's avatarBen Clayton <bclayton@google.com>
parent 9c258303
...@@ -222,16 +222,94 @@ func TestTree(t *testing.T) { ...@@ -222,16 +222,94 @@ func TestTree(t *testing.T) {
// ╭┴╮ ╭╯ // ╭┴╮ ╭╯
// v x z // v x z
checkSpans(t, tree.Spans(), span0, span1, span2, span3) checkSpans(t, tree.Spans(), span0, span1, span2, span3)
checkGroups(t, tree.FileSpanGroups(fileA), map[cov.SpanGroupID]cov.SpanGroup{
0: cov.SpanGroup{Spans: spans(0, 1)},
1: cov.SpanGroup{Spans: spans(0, 3)},
2: cov.SpanGroup{Spans: spans(1, 2)},
})
checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f:{l:{v x}} g:{n: {z} o}}}}`) checkTests(t, tree, `{a:{b:{d:{i} e:{j k}} c:{f:{l:{v x}} g:{n: {z} o}}}}`)
checkCoverage(t, tree, fileA, `a:{b:{d:{<0>} e:{[3]}} c:{f:{<2>} g:{n:{[2]} o:{<1>}}}}`) checkCoverage(t, tree, fileA, `a:{b:{d:{<0>} e:{[3]}} c:{f:{<2>} g:{n:{[2]} o:{<1>}}}}`)
} }
func TestTreeOptInvertForCommon(t *testing.T) {
tree := &cov.Tree{}
tree.Add(cov.Path{"a", "b"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "c"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "d"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "e"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "f"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "g"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "h"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "i"}, coverage(fileA, span0))
// (a)
// ┏━━━┳━━━┳━━━┳━┻━┳━━━┳━━━┳━━━┓
// (b) (c) (d) (e) (f) (g) (h) (i)
// [0] [0] [0] [1] [1] [0] [0] [0]
checkSpans(t, tree.Spans(), span0, span1)
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0]} c:{[0]} d:{[0]} e:{[1]} f:{[1]} g:{[0]} h:{[0]} i:{[0]}}`)
tree.Optimize()
// [0]
// (a)
// ╭───┬───┬───┲━┻━┱───┬───┬───╮
// b c d ┏┛ ┗┓ g h i
// (e) (f)
// <0> <0>
checkSpans(t, tree.Spans(), span0, span1)
checkGroups(t, tree.FileSpanGroups(fileA), map[cov.SpanGroupID]cov.SpanGroup{
0: cov.SpanGroup{Spans: spans(0, 1)},
})
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{[0] e:{<0>} f:{<0>}}`)
}
func TestTreeOptDontInvertForCommon(t *testing.T) {
tree := &cov.Tree{}
tree.Add(cov.Path{"a", "b"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "c"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "d"}, coverage(fileA, span0))
tree.Add(cov.Path{"a", "e"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "f"}, coverage(fileA, span1))
tree.Add(cov.Path{"a", "g"}, coverage(fileA, span2))
tree.Add(cov.Path{"a", "h"}, coverage(fileA, span2))
tree.Add(cov.Path{"a", "i"}, coverage(fileA, span2))
// (a)
// ┏━━━┳━━━┳━━━┳━┻━┳━━━┳━━━┳━━━┓
// (b) (c) (d) (e) (f) (g) (h) (i)
// [0] [0] [0] [1] [1] [2] [2] [2]
checkSpans(t, tree.Spans(), span0, span1, span2)
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0]} c:{[0]} d:{[0]} e:{[1]} f:{[1]} g:{[2]} h:{[2]} i:{[2]}}`)
tree.Optimize()
// (a)
// ┏━━━┳━━━┳━━━┳━┻━┳━━━┳━━━┳━━━┓
// (b) (c) (d) (e) (f) (g) (h) (i)
// [0] [0] [0] [1] [1] [2] [2] [2]
checkSpans(t, tree.Spans(), span0, span1, span2)
checkTests(t, tree, `{a:{b c d e f g h i}}`)
checkCoverage(t, tree, fileA, `a:{b:{[0]} c:{[0]} d:{[0]} e:{[1]} f:{[1]} g:{[2]} h:{[2]} i:{[2]}}`)
}
func checkSpans(t *testing.T, got []cov.Span, expect ...cov.Span) { func checkSpans(t *testing.T, got []cov.Span, expect ...cov.Span) {
if !reflect.DeepEqual(got, expect) { if !reflect.DeepEqual(got, expect) {
t.Errorf("Spans not as expected.\nGot: %+v\nExpect: %+v", got, expect) t.Errorf("Spans not as expected.\nGot: %+v\nExpect: %+v", got, expect)
} }
} }
func checkGroups(t *testing.T, got, expect map[cov.SpanGroupID]cov.SpanGroup) {
if !reflect.DeepEqual(got, expect) {
t.Errorf("SpanGroupss not as expected.\nGot: %+v\nExpect: %+v", got, expect)
}
}
func checkTests(t *testing.T, tree *cov.Tree, expect string) { func checkTests(t *testing.T, tree *cov.Tree, expect string) {
g, e := tree.Tests().String(tree.Strings()), expect g, e := tree.Tests().String(tree.Strings()), expect
if tg, te := trimWS(g), trimWS(e); tg != te { if tg, te := trimWS(g), trimWS(e); tg != te {
......
...@@ -33,6 +33,9 @@ func (t *Tree) Optimize() { ...@@ -33,6 +33,9 @@ func (t *Tree) Optimize() {
go func() { go func() {
defer wg.Done() defer wg.Done()
o := optimizer{} o := optimizer{}
for idx, tc := range file.tcm {
o.invertForCommon(tc, &t.testRoot.children[idx])
}
o.createGroups(file) o.createGroups(file)
}() }()
} }
...@@ -41,6 +44,8 @@ func (t *Tree) Optimize() { ...@@ -41,6 +44,8 @@ func (t *Tree) Optimize() {
type optimizer struct{} type optimizer struct{}
// createGroups looks for common SpanSets, and creates indexable span groups
// which are then used instead.
func (o *optimizer) createGroups(f *treeFile) { func (o *optimizer) createGroups(f *treeFile) {
const minSpansInGroup = 2 const minSpansInGroup = 2
...@@ -71,7 +76,7 @@ func (o *optimizer) createGroups(f *treeFile) { ...@@ -71,7 +76,7 @@ func (o *optimizer) createGroups(f *treeFile) {
spansets = append(spansets, &spansetInfo{ spansets = append(spansets, &spansetInfo{
key: key, key: key,
set: set, set: set,
grp: SpanGroup{spans: set}, grp: SpanGroup{Spans: set},
}) })
} }
...@@ -100,8 +105,8 @@ nextSpan: ...@@ -100,8 +105,8 @@ nextSpan:
for _, b := range spansets[i+1:] { for _, b := range spansets[i+1:] {
if len(a.set) > len(b.set) && a.set.containsAll(b.set) { if len(a.set) > len(b.set) && a.set.containsAll(b.set) {
extend := b.id // Do not take address of iterator! extend := b.id // Do not take address of iterator!
a.grp.spans = a.set.sub(b.set) a.grp.Spans = a.set.removeAll(b.set)
a.grp.extend = &extend a.grp.Extend = &extend
continue nextSpan continue nextSpan
} }
} }
...@@ -128,3 +133,39 @@ nextSpan: ...@@ -128,3 +133,39 @@ nextSpan:
} }
}) })
} }
// invertCommon looks for tree nodes with the majority of the child nodes with
// the same spans. This span is promoted up to the parent, and the children
// have the span inverted.
func (o *optimizer) invertForCommon(tc *TestCoverage, t *Test) {
wg := sync.WaitGroup{}
wg.Add(len(tc.Children))
for id, child := range tc.Children {
id, child := id, child
go func() {
defer wg.Done()
o.invertForCommon(child, &t.children[id])
}()
}
wg.Wait()
counts := map[SpanID]int{}
for _, child := range tc.Children {
for span := range child.Spans {
counts[span] = counts[span] + 1
}
}
for span, count := range counts {
if count > len(t.children)/2 {
tc.Spans = tc.Spans.invert(span)
for _, idx := range t.indices {
child := tc.Children.index(idx)
child.Spans = child.Spans.invert(span)
if child.deletable() {
delete(tc.Children, idx)
}
}
}
}
}
...@@ -176,16 +176,16 @@ func (t *Tree) writeSpanGroupsJSON(spangroups map[SpanGroupID]SpanGroup, sb *str ...@@ -176,16 +176,16 @@ func (t *Tree) writeSpanGroupsJSON(spangroups map[SpanGroupID]SpanGroup, sb *str
func (t *Tree) writeSpanGroupJSON(group SpanGroup, sb *strings.Builder) { func (t *Tree) writeSpanGroupJSON(group SpanGroup, sb *strings.Builder) {
sb.WriteString(`{`) sb.WriteString(`{`)
sb.WriteString(`"s":[`) sb.WriteString(`"s":[`)
for i, spanID := range group.spans.List() { for i, spanID := range group.Spans.List() {
if i > 0 { if i > 0 {
sb.WriteString(`,`) sb.WriteString(`,`)
} }
sb.WriteString(fmt.Sprintf("%v", spanID)) sb.WriteString(fmt.Sprintf("%v", spanID))
} }
sb.WriteString(`]`) sb.WriteString(`]`)
if group.extend != nil { if group.Extend != nil {
sb.WriteString(`,"e":`) sb.WriteString(`,"e":`)
sb.WriteString(fmt.Sprintf("%v", *group.extend)) sb.WriteString(fmt.Sprintf("%v", *group.Extend))
} }
sb.WriteString(`}`) sb.WriteString(`}`)
} }
...@@ -349,11 +349,11 @@ func (p *parser) parseSpanGroups() map[SpanGroupID]SpanGroup { ...@@ -349,11 +349,11 @@ func (p *parser) parseSpanGroups() map[SpanGroupID]SpanGroup {
case "s": case "s":
p.array(func(spanIdx int) { p.array(func(spanIdx int) {
id := SpanID(p.integer()) id := SpanID(p.integer())
g.spans[id] = struct{}{} g.Spans[id] = struct{}{}
}) })
case "e": case "e":
extend := SpanGroupID(p.integer()) extend := SpanGroupID(p.integer())
g.extend = &extend g.Extend = &extend
} }
}) })
spangroups[SpanGroupID(groupIdx)] = g spangroups[SpanGroupID(groupIdx)] = g
......
...@@ -76,14 +76,18 @@ func (l SpanList) Compare(o SpanList) int { ...@@ -76,14 +76,18 @@ func (l SpanList) Compare(o SpanList) int {
// Spans returns all the spans used by the tree // Spans returns all the spans used by the tree
func (t *Tree) Spans() SpanList { func (t *Tree) Spans() SpanList {
out := make(SpanList, 0, len(t.spans)) out := make(SpanList, len(t.spans))
for span := range t.spans { for span, id := range t.spans {
out = append(out, span) out[id] = span
} }
sort.Slice(out, func(i, j int) bool { return out[i].Before(out[j]) })
return out return out
} }
// FileSpanGroups returns all the span groups for the given file
func (t *Tree) FileSpanGroups(path string) map[SpanGroupID]SpanGroup {
return t.files[path].spangroups
}
// FileCoverage returns the TestCoverageMap for the given file // FileCoverage returns the TestCoverageMap for the given file
func (t *Tree) FileCoverage(path string) TestCoverageMap { func (t *Tree) FileCoverage(path string) TestCoverageMap {
return t.files[path].tcm return t.files[path].tcm
...@@ -143,7 +147,7 @@ nextFile: ...@@ -143,7 +147,7 @@ nextFile:
for _, indexedTest := range tests { for _, indexedTest := range tests {
if indexedTest.created { if indexedTest.created {
if parent != nil && len(test.children) == 1 { if parent != nil && len(test.children) == 1 {
parent.Spans = parent.Spans.add(spans) parent.Spans = parent.Spans.addAll(spans)
delete(parent.Children, indexedTest.index) delete(parent.Children, indexedTest.index)
} else { } else {
tc := tcm.index(indexedTest.index) tc := tcm.index(indexedTest.index)
...@@ -157,19 +161,19 @@ nextFile: ...@@ -157,19 +161,19 @@ nextFile:
// If the tree node contains spans that are not in this new test, // If the tree node contains spans that are not in this new test,
// we need to push those spans down to all the other children. // we need to push those spans down to all the other children.
if lower := tc.Spans.sub(spans); len(lower) > 0 { if lower := tc.Spans.removeAll(spans); len(lower) > 0 {
// push into each child node // push into each child node
for i := range test.children { for i := range test.children {
child := tc.Children.index(TestIndex(i)) child := tc.Children.index(TestIndex(i))
child.Spans = child.Spans.add(lower) child.Spans = child.Spans.addAll(lower)
} }
// remove from node // remove from node
tc.Spans = tc.Spans.sub(lower) tc.Spans = tc.Spans.removeAll(lower)
} }
// The spans that are in the new test, but are not part of the tree // The spans that are in the new test, but are not part of the tree
// node carry propagating down. // node carry propagating down.
spans = spans.sub(tc.Spans) spans = spans.removeAll(tc.Spans)
if len(spans) == 0 { if len(spans) == 0 {
continue nextFile continue nextFile
} }
...@@ -288,9 +292,15 @@ func (tc TestCoverage) String(t *Test, s Strings) string { ...@@ -288,9 +292,15 @@ func (tc TestCoverage) String(t *Test, s Strings) string {
return sb.String() return sb.String()
} }
// deletable returns true if the TestCoverage provides no data.
func (tc TestCoverage) deletable() bool {
return len(tc.Spans) == 0 && tc.Group == nil && len(tc.Children) == 0
}
// TestCoverageMap is a map of TestIndex to *TestCoverage. // TestCoverageMap is a map of TestIndex to *TestCoverage.
type TestCoverageMap map[TestIndex]*TestCoverage type TestCoverageMap map[TestIndex]*TestCoverage
// traverse performs a depth first traversal of the TestCoverage tree.
func (tcm TestCoverageMap) traverse(cb func(*TestCoverage)) { func (tcm TestCoverageMap) traverse(cb func(*TestCoverage)) {
for _, tc := range tcm { for _, tc := range tcm {
cb(tc) cb(tc)
...@@ -380,16 +390,31 @@ func (s SpanSet) String() string { ...@@ -380,16 +390,31 @@ func (s SpanSet) String() string {
return sb.String() return sb.String()
} }
func (s SpanSet) contains(rhs SpanID) bool {
_, found := s[rhs]
return found
}
func (s SpanSet) containsAll(rhs SpanSet) bool { func (s SpanSet) containsAll(rhs SpanSet) bool {
for span := range rhs { for span := range rhs {
if _, found := s[span]; !found { if !s.contains(span) {
return false return false
} }
} }
return true return true
} }
func (s SpanSet) sub(rhs SpanSet) SpanSet { func (s SpanSet) remove(rhs SpanID) SpanSet {
out := make(SpanSet, len(s))
for span := range s {
if span != rhs {
out[span] = struct{}{}
}
}
return out
}
func (s SpanSet) removeAll(rhs SpanSet) SpanSet {
out := make(SpanSet, len(s)) out := make(SpanSet, len(s))
for span := range s { for span := range s {
if _, found := rhs[span]; !found { if _, found := rhs[span]; !found {
...@@ -399,7 +424,16 @@ func (s SpanSet) sub(rhs SpanSet) SpanSet { ...@@ -399,7 +424,16 @@ func (s SpanSet) sub(rhs SpanSet) SpanSet {
return out return out
} }
func (s SpanSet) add(rhs SpanSet) SpanSet { func (s SpanSet) add(rhs SpanID) SpanSet {
out := make(SpanSet, len(s)+1)
for span := range s {
out[span] = struct{}{}
}
out[rhs] = struct{}{}
return out
}
func (s SpanSet) addAll(rhs SpanSet) SpanSet {
out := make(SpanSet, len(s)+len(rhs)) out := make(SpanSet, len(s)+len(rhs))
for span := range s { for span := range s {
out[span] = struct{}{} out[span] = struct{}{}
...@@ -410,18 +444,25 @@ func (s SpanSet) add(rhs SpanSet) SpanSet { ...@@ -410,18 +444,25 @@ func (s SpanSet) add(rhs SpanSet) SpanSet {
return out return out
} }
func (s SpanSet) invert(rhs SpanID) SpanSet {
if s.contains(rhs) {
return s.remove(rhs)
}
return s.add(rhs)
}
// SpanGroupID is an identifier of a SpanGroup. // SpanGroupID is an identifier of a SpanGroup.
type SpanGroupID int type SpanGroupID int
// SpanGroup holds a number of spans, potentially extending from another // SpanGroup holds a number of spans, potentially extending from another
// SpanGroup. // SpanGroup.
type SpanGroup struct { type SpanGroup struct {
spans SpanSet Spans SpanSet
extend *SpanGroupID Extend *SpanGroupID
} }
func newSpanGroup() SpanGroup { func newSpanGroup() SpanGroup {
return SpanGroup{spans: SpanSet{}} return SpanGroup{Spans: SpanSet{}}
} }
func indent(s string) string { func indent(s string) string {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment