// optimizeOrder(l) takes a list and returns a list, containing the same contents // but with a new ordering, however it wishes. func (it *And) optimizeOrder(its []graph.Iterator) []graph.Iterator { var ( // bad contains iterators that can't be (efficiently) nexted, such as // graph.Optional or graph.Not. Separate them out and tack them on at the end. out, bad []graph.Iterator best graph.Iterator bestCost = int64(1 << 62) ) // Find the iterator with the projected "best" total cost. // Total cost is defined as The Next()ed iterator's cost to Next() out // all of it's contents, and to Contains() each of those against everyone // else. for _, root := range its { if _, canNext := root.(graph.Nexter); !canNext { bad = append(bad, root) continue } rootStats := root.Stats() cost := rootStats.NextCost for _, f := range its { if _, canNext := f.(graph.Nexter); !canNext { continue } if f == root { continue } stats := f.Stats() cost += stats.ContainsCost * (1 + (rootStats.Size / (stats.Size + 1))) } cost *= rootStats.Size if clog.V(3) { clog.Infof("And: %v Root: %v Total Cost: %v Best: %v", it.UID(), root.UID(), cost, bestCost) } if cost < bestCost { best = root bestCost = cost } } if clog.V(3) { clog.Infof("And: %v Choosing: %v Best: %v", it.UID(), best.UID(), bestCost) } // TODO(barakmich): Optimization of order need not stop here. Picking a smart // Contains() order based on probability of getting a false Contains() first is // useful (fail faster). // Put the best iterator (the one we wish to Next()) at the front... out = append(out, best) // ... push everyone else after... for _, it := range its { if _, canNext := it.(graph.Nexter); !canNext { continue } if it != best { out = append(out, it) } } // ...and finally, the difficult children on the end. return append(out, bad...) }