Skip to content

Commit 71a7dd2

Browse files
committed
opt: make CostFlags an ordered bitmask
`memo.CostFlags` is now an ordered bitmask where each bit indicates a specific cost penalty and the penalties are ordered by precedence. The highest precedence penalty uses the highest-order bit. This allows CostFlags to be easily compared with built-in comparison operators (>, <, =). Release note: None
1 parent 0fed78c commit 71a7dd2

File tree

5 files changed

+48
-107
lines changed

5 files changed

+48
-107
lines changed

pkg/sql/opt/memo/cost.go

Lines changed: 27 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,14 @@ type Cost struct {
2727
// group members during testing, by setting their cost so high that any other
2828
// member will have a lower cost.
2929
var MaxCost = Cost{
30-
C: math.Inf(+1),
31-
Flags: CostFlags{
32-
FullScanPenalty: true,
33-
HugeCostPenalty: true,
34-
UnboundedCardinality: true,
35-
},
30+
C: math.Inf(+1),
31+
Flags: HugeCostPenalty | FullScanPenalty | UnboundedCardinality,
3632
}
3733

3834
// Less returns true if this cost is lower than the given cost.
3935
func (c Cost) Less(other Cost) bool {
4036
if c.Flags != other.Flags {
41-
return c.Flags.Less(other.Flags)
37+
return c.Flags < other.Flags
4238
}
4339
// Two plans with the same cost can have slightly different floating point
4440
// results (e.g. same subcosts being added up in a different order). So we
@@ -56,7 +52,7 @@ func (c Cost) Less(other Cost) bool {
5652
// Add adds the other cost to this cost.
5753
func (c *Cost) Add(other Cost) {
5854
c.C += other.C
59-
c.Flags.Add(other.Flags)
55+
c.Flags |= other.Flags
6056
if c.aux.fullScanCount > math.MaxUint8-other.aux.fullScanCount {
6157
// Avoid overflow.
6258
c.aux.fullScanCount = math.MaxUint8
@@ -79,48 +75,31 @@ func (c *Cost) IncrFullScanCount() {
7975
c.aux.fullScanCount++
8076
}
8177

82-
// CostFlags contains flags that penalize the cost of an operator.
83-
type CostFlags struct {
84-
// FullScanPenalty is true if the cost of a full table or index scan is
85-
// penalized, indicating that a full scan should only be used if no other plan
86-
// is possible.
87-
FullScanPenalty bool
78+
// CostFlags is an ordered bitmask where each bit indicates a cost penalty. The
79+
// penalties are ordered by precedence, with the highest precedence penalty
80+
// using the highest-order bit. This allows CostFlags to be easily compared with
81+
// built-in comparison operators (>, <, =, etc.). For example, CostFlags with
82+
// HugeCostPenalty will always be greater than CostFlags without.
83+
type CostFlags uint8
84+
85+
const (
8886
// HugeCostPenalty is true if a plan should be avoided at all costs. This is
89-
// used when the optimizer is forced to use a particular plan, and will error
90-
// if it cannot be used.
91-
HugeCostPenalty bool
87+
// used when the optimizer is forced to use a particular plan, and will
88+
// error if it cannot be used. It takes precedence over other penalties,
89+
// since it indicates that a plan is being forced with a hint, and will
90+
// error if we cannot comply with the hint.
91+
HugeCostPenalty CostFlags = 1 << (7 - iota)
92+
93+
// FullScanPenalty is true if the cost of a full table or index scan is
94+
// penalized, indicating that a full scan should only be used if no other
95+
// plan is possible.
96+
FullScanPenalty
97+
9298
// UnboundedCardinality is true if the operator or any of its descendants
9399
// have no guaranteed upperbound on the number of rows that they can
94100
// produce. See props.AnyCardinality.
95-
UnboundedCardinality bool
96-
}
97-
98-
// Less returns true if these flags indicate a lower penalty than the other
99-
// CostFlags.
100-
func (c CostFlags) Less(other CostFlags) bool {
101-
// HugeCostPenalty takes precedence over other penalties, since it indicates
102-
// that a plan is being forced with a hint, and will error if we cannot comply
103-
// with the hint.
104-
if c.HugeCostPenalty != other.HugeCostPenalty {
105-
return !c.HugeCostPenalty
106-
}
107-
if c.FullScanPenalty != other.FullScanPenalty {
108-
return !c.FullScanPenalty
109-
}
110-
if c.UnboundedCardinality != other.UnboundedCardinality {
111-
return !c.UnboundedCardinality
112-
}
113-
return false
114-
}
101+
UnboundedCardinality
115102

116-
// Add adds the other flags to these flags.
117-
func (c *CostFlags) Add(other CostFlags) {
118-
c.FullScanPenalty = c.FullScanPenalty || other.FullScanPenalty
119-
c.HugeCostPenalty = c.HugeCostPenalty || other.HugeCostPenalty
120-
c.UnboundedCardinality = c.UnboundedCardinality || other.UnboundedCardinality
121-
}
122-
123-
// Empty returns true if these flags are empty.
124-
func (c CostFlags) Empty() bool {
125-
return !c.FullScanPenalty && !c.HugeCostPenalty && !c.UnboundedCardinality
126-
}
103+
// NoPenalties represents no penalties.
104+
NoPenalties CostFlags = 0
105+
)

pkg/sql/opt/memo/cost_test.go

Lines changed: 11 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ func TestCostLess(t *testing.T) {
2626
{Cost{C: 1}, Cost{C: 1.00000001}, true},
2727
{Cost{C: 1000}, Cost{C: 1000.00000000001}, false},
2828
{Cost{C: 1000}, Cost{C: 1000.00001}, true},
29-
{Cost{C: 1.0, Flags: CostFlags{FullScanPenalty: true}}, Cost{C: 1.0}, false},
30-
{Cost{C: 1.0}, Cost{C: 1.0, Flags: CostFlags{HugeCostPenalty: true}}, true},
31-
{Cost{C: 1.0, Flags: CostFlags{FullScanPenalty: true, HugeCostPenalty: true}}, Cost{C: 1.0}, false},
32-
{Cost{C: 1.0, Flags: CostFlags{FullScanPenalty: true}}, Cost{C: 1.0, Flags: CostFlags{HugeCostPenalty: true}}, true},
29+
{Cost{C: 1.0, Flags: FullScanPenalty}, Cost{C: 1.0}, false},
30+
{Cost{C: 1.0}, Cost{C: 1.0, Flags: HugeCostPenalty}, true},
31+
{Cost{C: 1.0, Flags: FullScanPenalty | HugeCostPenalty}, Cost{C: 1.0}, false},
32+
{Cost{C: 1.0, Flags: FullScanPenalty}, Cost{C: 1.0, Flags: HugeCostPenalty}, true},
3333
{MaxCost, Cost{C: 1.0}, false},
3434
{Cost{C: 0.0}, MaxCost, true},
3535
{MaxCost, MaxCost, false},
36-
{MaxCost, Cost{C: 1.0, Flags: CostFlags{FullScanPenalty: true}}, false},
37-
{Cost{C: 1.0, Flags: CostFlags{HugeCostPenalty: true}}, MaxCost, true},
38-
{Cost{C: 2.0, Flags: CostFlags{}}, Cost{C: 1.0, Flags: CostFlags{UnboundedCardinality: true}}, true},
39-
{Cost{C: 1.0, Flags: CostFlags{UnboundedCardinality: true}}, Cost{C: 2.0, Flags: CostFlags{}}, false},
36+
{MaxCost, Cost{C: 1.0, Flags: FullScanPenalty}, false},
37+
{Cost{C: 1.0, Flags: HugeCostPenalty}, MaxCost, true},
38+
{Cost{C: 2.0}, Cost{C: 1.0, Flags: UnboundedCardinality}, true},
39+
{Cost{C: 1.0, Flags: UnboundedCardinality}, Cost{C: 2.0}, false},
4040
// Auxiliary information should not affect the comparison.
4141
{Cost{C: 1.0, aux: testAux{0}}, Cost{C: 1.0, aux: testAux{1}}, false},
4242
}
@@ -55,8 +55,9 @@ func TestCostAdd(t *testing.T) {
5555
{Cost{C: 0.0}, Cost{C: 0.0}, Cost{C: 0.0}},
5656
{Cost{C: -1.0}, Cost{C: 1.0}, Cost{C: 0.0}},
5757
{Cost{C: 1.5}, Cost{C: 2.5}, Cost{C: 4.0}},
58-
{Cost{C: 1.0, Flags: CostFlags{FullScanPenalty: true}}, Cost{C: 2.0}, Cost{C: 3.0, Flags: CostFlags{FullScanPenalty: true}}},
59-
{Cost{C: 1.0}, Cost{C: 2.0, Flags: CostFlags{HugeCostPenalty: true}}, Cost{C: 3.0, Flags: CostFlags{HugeCostPenalty: true}}},
58+
{Cost{C: 1.0, Flags: FullScanPenalty}, Cost{C: 2.0}, Cost{C: 3.0, Flags: FullScanPenalty}},
59+
{Cost{C: 1.0}, Cost{C: 2.0, Flags: HugeCostPenalty}, Cost{C: 3.0, Flags: HugeCostPenalty}},
60+
{Cost{C: 1.0, Flags: UnboundedCardinality}, Cost{C: 2.0, Flags: HugeCostPenalty}, Cost{C: 3.0, Flags: HugeCostPenalty | UnboundedCardinality}},
6061
{Cost{C: 1.0, aux: testAux{1}}, Cost{C: 1.0, aux: testAux{2}}, Cost{C: 2.0, aux: testAux{3}}},
6162
{Cost{C: 1.0, aux: testAux{200}}, Cost{C: 1.0, aux: testAux{100}}, Cost{C: 2.0, aux: testAux{255}}},
6263
}
@@ -67,41 +68,3 @@ func TestCostAdd(t *testing.T) {
6768
}
6869
}
6970
}
70-
71-
func TestCostFlagsLess(t *testing.T) {
72-
testCases := []struct {
73-
left, right CostFlags
74-
expected bool
75-
}{
76-
{CostFlags{FullScanPenalty: false, HugeCostPenalty: false}, CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, true},
77-
{CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, CostFlags{FullScanPenalty: false, HugeCostPenalty: false}, false},
78-
{CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, false},
79-
{CostFlags{FullScanPenalty: false}, CostFlags{FullScanPenalty: true}, true},
80-
{CostFlags{HugeCostPenalty: false}, CostFlags{HugeCostPenalty: true}, true},
81-
{CostFlags{UnboundedCardinality: false}, CostFlags{UnboundedCardinality: true}, true},
82-
{CostFlags{UnboundedCardinality: true}, CostFlags{UnboundedCardinality: false}, false},
83-
}
84-
for _, tc := range testCases {
85-
if tc.left.Less(tc.right) != tc.expected {
86-
t.Errorf("expected %v.Less(%v) to be %v", tc.left, tc.right, tc.expected)
87-
}
88-
}
89-
}
90-
91-
func TestCostFlagsAdd(t *testing.T) {
92-
testCases := []struct {
93-
left, right, expected CostFlags
94-
}{
95-
{CostFlags{FullScanPenalty: false, HugeCostPenalty: false}, CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, CostFlags{FullScanPenalty: true, HugeCostPenalty: true}},
96-
{CostFlags{FullScanPenalty: true, HugeCostPenalty: true}, CostFlags{FullScanPenalty: false, HugeCostPenalty: false}, CostFlags{FullScanPenalty: true, HugeCostPenalty: true}},
97-
{CostFlags{FullScanPenalty: false}, CostFlags{FullScanPenalty: true}, CostFlags{FullScanPenalty: true}},
98-
{CostFlags{HugeCostPenalty: false}, CostFlags{HugeCostPenalty: true}, CostFlags{HugeCostPenalty: true}},
99-
{CostFlags{FullScanPenalty: true, HugeCostPenalty: false}, CostFlags{FullScanPenalty: false, HugeCostPenalty: true}, CostFlags{FullScanPenalty: true, HugeCostPenalty: true}},
100-
}
101-
for _, tc := range testCases {
102-
tc.left.Add(tc.right)
103-
if tc.left != tc.expected {
104-
t.Errorf("expected %v.Add(%v) to be %v, got %v", tc.left, tc.right, tc.expected, tc.left)
105-
}
106-
}
107-
}

pkg/sql/opt/memo/expr_format.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -919,16 +919,16 @@ func (f *ExprFmtCtx) formatRelational(e RelExpr, tp treeprinter.Node) {
919919
if cost.C != 0 {
920920
tp.Childf("cost: %.9g", cost.C)
921921
}
922-
if !cost.Flags.Empty() {
922+
if cost.Flags != NoPenalties {
923923
var b strings.Builder
924924
b.WriteString("cost-flags:")
925-
if cost.Flags.FullScanPenalty {
925+
if cost.Flags&FullScanPenalty != 0 {
926926
b.WriteString(" full-scan-penalty")
927927
}
928-
if cost.Flags.HugeCostPenalty {
928+
if cost.Flags&HugeCostPenalty != 0 {
929929
b.WriteString(" huge-cost-penalty")
930930
}
931-
if cost.Flags.UnboundedCardinality {
931+
if cost.Flags&UnboundedCardinality != 0 {
932932
b.WriteString(" unbounded-cardinality")
933933
}
934934
tp.Child(b.String())

pkg/sql/opt/xform/coster.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ var (
190190
// that "violate" a hint like forcing a specific index or join algorithm.
191191
// If the final expression has this cost or larger, it means that there was no
192192
// plan that could satisfy the hints.
193-
hugeCost = memo.Cost{C: 1e100, Flags: memo.CostFlags{HugeCostPenalty: true}}
193+
hugeCost = memo.Cost{C: 1e100, Flags: memo.HugeCostPenalty}
194194

195195
// SmallDistributeCost is the per-operation cost overhead for scans which may
196196
// access remote regions, but the scanned table is unpartitioned with no lease
@@ -218,7 +218,7 @@ var (
218218
// the correct plan is found?
219219
LargeDistributeCostWithHomeRegion = memo.Cost{
220220
C: LargeDistributeCost.C / 2,
221-
Flags: memo.CostFlags{HugeCostPenalty: true},
221+
Flags: memo.HugeCostPenalty,
222222
}
223223
)
224224

@@ -645,7 +645,7 @@ func (c *coster) ComputeCost(candidate memo.RelExpr, required *physical.Required
645645
if candidate.Relational().Cardinality.IsUnbounded() {
646646
cost.C += cpuCostFactor
647647
if c.evalCtx.SessionData().OptimizerPreferBoundedCardinality {
648-
cost.Flags.UnboundedCardinality = true
648+
cost.Flags |= memo.UnboundedCardinality
649649
}
650650
}
651651

@@ -909,7 +909,7 @@ func (c *coster) computeScanCost(scan *memo.ScanExpr, required *physical.Require
909909
cost.IncrFullScanCount()
910910
if scan.Flags.AvoidFullScan {
911911
// Apply a penalty for a full scan if needed.
912-
cost.Flags.FullScanPenalty = true
912+
cost.Flags |= memo.FullScanPenalty
913913
}
914914
}
915915

pkg/sql/prep/statement.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,9 @@ func (p *planCosts) NumCustom() int {
187187
// average cost of the custom plans.
188188
func (p *planCosts) IsGenericOptimal() bool {
189189
// Check cost flags and full scan counts.
190-
if gc := p.generic.FullScanCount(); gc > 0 || !p.generic.Flags.Empty() {
190+
if gc := p.generic.FullScanCount(); gc > 0 || p.generic.Flags != memo.NoPenalties {
191191
for i := 0; i < p.custom.length; i++ {
192-
if p.custom.costs[i].Flags.Less(p.generic.Flags) ||
193-
gc > p.custom.costs[i].FullScanCount() {
192+
if p.custom.costs[i].Flags < p.generic.Flags || gc > p.custom.costs[i].FullScanCount() {
194193
return false
195194
}
196195
}

0 commit comments

Comments
 (0)