| // Copyright 2020 The SwiftShader Authors. All Rights Reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package cov | |
| import ( | |
| "log" | |
| "sort" | |
| "sync" | |
| ) | |
| // Optimize optimizes the Tree by de-duplicating common spans into a tree of | |
| // SpanGroups. | |
| func (t *Tree) Optimize() { | |
| log.Printf("Optimizing coverage tree...") | |
| // Start by gathering all of the unique spansets | |
| wg := sync.WaitGroup{} | |
| wg.Add(len(t.files)) | |
| for _, file := range t.files { | |
| file := file | |
| go func() { | |
| defer wg.Done() | |
| o := optimizer{} | |
| for idx, tc := range file.tcm { | |
| o.invertForCommon(tc, &t.testRoot.children[idx]) | |
| } | |
| o.createGroups(file) | |
| }() | |
| } | |
| wg.Wait() | |
| } | |
| type optimizer struct{} | |
| // createGroups looks for common SpanSets, and creates indexable span groups | |
| // which are then used instead. | |
| func (o *optimizer) createGroups(f *treeFile) { | |
| const minSpansInGroup = 2 | |
| type spansetKey string | |
| spansetMap := map[spansetKey]SpanSet{} | |
| f.tcm.traverse(func(tc *TestCoverage) { | |
| if len(tc.Spans) >= minSpansInGroup { | |
| key := spansetKey(tc.Spans.String()) | |
| if _, ok := spansetMap[key]; !ok { | |
| spansetMap[key] = tc.Spans | |
| } | |
| } | |
| }) | |
| if len(spansetMap) == 0 { | |
| return | |
| } | |
| type spansetInfo struct { | |
| key spansetKey | |
| set SpanSet // fully expanded set | |
| grp SpanGroup | |
| id SpanGroupID | |
| } | |
| spansets := make([]*spansetInfo, 0, len(spansetMap)) | |
| for key, set := range spansetMap { | |
| spansets = append(spansets, &spansetInfo{ | |
| key: key, | |
| set: set, | |
| grp: SpanGroup{Spans: set}, | |
| }) | |
| } | |
| // Sort by number of spans in each sets starting with the largest. | |
| sort.Slice(spansets, func(i, j int) bool { | |
| a, b := spansets[i].set, spansets[j].set | |
| switch { | |
| case len(a) > len(b): | |
| return true | |
| case len(a) < len(b): | |
| return false | |
| } | |
| return a.List().Compare(b.List()) == -1 // Just to keep output stable | |
| }) | |
| // Assign IDs now that we have stable order. | |
| for i := range spansets { | |
| spansets[i].id = SpanGroupID(i) | |
| } | |
| // Loop over the spanGroups starting from the largest, and try to fold them | |
| // into the larger sets. | |
| // This is O(n^2) complexity. | |
| nextSpan: | |
| for i, a := range spansets[:len(spansets)-1] { | |
| for _, b := range spansets[i+1:] { | |
| if len(a.set) > len(b.set) && a.set.containsAll(b.set) { | |
| extend := b.id // Do not take address of iterator! | |
| a.grp.Spans = a.set.removeAll(b.set) | |
| a.grp.Extend = &extend | |
| continue nextSpan | |
| } | |
| } | |
| } | |
| // Rebuild a map of spansetKey to SpanGroup | |
| spangroupMap := make(map[spansetKey]*spansetInfo, len(spansets)) | |
| for _, s := range spansets { | |
| spangroupMap[s.key] = s | |
| } | |
| // Store the groups in the tree | |
| f.spangroups = make(map[SpanGroupID]SpanGroup, len(spansets)) | |
| for _, s := range spansets { | |
| f.spangroups[s.id] = s.grp | |
| } | |
| // Update all the uses. | |
| f.tcm.traverse(func(tc *TestCoverage) { | |
| key := spansetKey(tc.Spans.String()) | |
| if g, ok := spangroupMap[key]; ok { | |
| tc.Spans = nil | |
| tc.Group = &g.id | |
| } | |
| }) | |
| } | |
| // 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) | |
| } | |
| } | |
| } | |
| } | |
| } |