Skip to content

Commit 26e64e9

Browse files
authored
Cost-based decision between ORDER and SORT plans (#8316)
* Cost-based decision between ORDER and SORT plans * Use inline constexpr as suggested by Adriano. Misc style changes.
1 parent 65b8050 commit 26e64e9

File tree

3 files changed

+103
-22
lines changed

3 files changed

+103
-22
lines changed

src/jrd/optimizer/Optimizer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2646,7 +2646,7 @@ RecordSource* Optimizer::generateRetrieval(StreamType stream,
26462646
}
26472647
}
26482648

2649-
const auto navigation = retrieval.getNavigation();
2649+
const auto navigation = retrieval.getNavigation(candidate);
26502650

26512651
if (navigation)
26522652
{

src/jrd/optimizer/Optimizer.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,29 +46,30 @@ namespace Jrd {
4646

4747
// AB: 2005-11-05
4848
// Constants below needs some discussions and ideas
49-
const double REDUCE_SELECTIVITY_FACTOR_EQUALITY = 0.001;
50-
const double REDUCE_SELECTIVITY_FACTOR_BETWEEN = 0.0025;
51-
const double REDUCE_SELECTIVITY_FACTOR_LESS = 0.05;
52-
const double REDUCE_SELECTIVITY_FACTOR_GREATER = 0.05;
53-
const double REDUCE_SELECTIVITY_FACTOR_STARTING = 0.01;
54-
const double REDUCE_SELECTIVITY_FACTOR_OTHER = 0.01;
49+
inline constexpr double REDUCE_SELECTIVITY_FACTOR_EQUALITY = 0.001;
50+
inline constexpr double REDUCE_SELECTIVITY_FACTOR_BETWEEN = 0.0025;
51+
inline constexpr double REDUCE_SELECTIVITY_FACTOR_LESS = 0.05;
52+
inline constexpr double REDUCE_SELECTIVITY_FACTOR_GREATER = 0.05;
53+
inline constexpr double REDUCE_SELECTIVITY_FACTOR_STARTING = 0.01;
54+
inline constexpr double REDUCE_SELECTIVITY_FACTOR_OTHER = 0.01;
5555

5656
// Cost of simple (CPU bound) operations is less than the page access cost
57-
const double COST_FACTOR_MEMCOPY = 0.5;
58-
const double COST_FACTOR_HASHING = 0.5;
57+
inline constexpr double COST_FACTOR_MEMCOPY = 0.5;
58+
inline constexpr double COST_FACTOR_HASHING = 0.5;
59+
inline constexpr double COST_FACTOR_QUICKSORT = 0.1;
5960

60-
const double MAXIMUM_SELECTIVITY = 1.0;
61-
const double DEFAULT_SELECTIVITY = 0.1;
61+
inline constexpr double MAXIMUM_SELECTIVITY = 1.0;
62+
inline constexpr double DEFAULT_SELECTIVITY = 0.1;
6263

63-
const double MINIMUM_CARDINALITY = 1.0;
64-
const double THRESHOLD_CARDINALITY = 5.0;
65-
const double DEFAULT_CARDINALITY = 1000.0;
64+
inline constexpr double MINIMUM_CARDINALITY = 1.0;
65+
inline constexpr double THRESHOLD_CARDINALITY = 5.0;
66+
inline constexpr double DEFAULT_CARDINALITY = 1000.0;
6667

6768
// Default depth of an index tree (including one leaf page),
6869
// also representing the minimal cost of the index scan.
6970
// We assume that the root page would be always cached,
7071
// so it's not included here.
71-
const double DEFAULT_INDEX_COST = 3.0;
72+
inline const double DEFAULT_INDEX_COST = 3.0;
7273

7374

7475
struct index_desc;
@@ -684,7 +685,7 @@ class Retrieval : private Firebird::PermanentStorage
684685
}
685686

686687
InversionCandidate* getInversion();
687-
IndexTableScan* getNavigation();
688+
IndexTableScan* getNavigation(const InversionCandidate* candidate);
688689

689690
protected:
690691
void analyzeNavigation(const InversionCandidateList& inversions);

src/jrd/optimizer/Retrieval.cpp

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@
4949

5050
#include "../jrd/optimizer/Optimizer.h"
5151

52+
#include <cmath>
53+
5254
using namespace Firebird;
5355
using namespace Jrd;
5456

@@ -366,25 +368,103 @@ InversionCandidate* Retrieval::getInversion()
366368
return invCandidate;
367369
}
368370

369-
IndexTableScan* Retrieval::getNavigation()
371+
IndexTableScan* Retrieval::getNavigation(const InversionCandidate* candidate)
370372
{
371373
if (!navigationCandidate)
372374
return nullptr;
373375

374-
IndexScratch* const scratch = navigationCandidate->scratch;
376+
const auto scratch = navigationCandidate->scratch;
377+
378+
const auto streamCardinality = csb->csb_rpt[stream].csb_cardinality;
379+
380+
// If the table looks like empty during preparation time, we cannot be sure about
381+
// its real cardinality during execution. So, unless we have some index-based
382+
// filtering applied, let's better be pessimistic and avoid external sorting
383+
// due to likely cardinality under-estimation.
384+
const bool avoidSorting = (streamCardinality <= MINIMUM_CARDINALITY && !candidate->inversion);
385+
386+
if (!(scratch->index->idx_runtime_flags & idx_plan_navigate) && !avoidSorting)
387+
{
388+
// Check whether the navigational index scan is cheaper than the external sort
389+
// and give up if it's not worth the efforts.
390+
//
391+
// We ignore candidate->cost in the calculations below as it belongs
392+
// to both parts being compared.
393+
394+
fb_assert(candidate);
395+
396+
// Restore the original selectivity of the inversion,
397+
// i.e. before the navigation candidate was accounted
398+
auto selectivity = candidate->selectivity / navigationCandidate->selectivity;
399+
400+
// Non-indexed booleans are checked before sorting, so they improve the selectivity
401+
402+
double factor = MAXIMUM_SELECTIVITY;
403+
for (auto iter = optimizer->getConjuncts(outerFlag, innerFlag); iter.hasData(); ++iter)
404+
{
405+
if (!(iter & Optimizer::CONJUNCT_USED) &&
406+
!candidate->matches.exist(iter) &&
407+
iter->computable(csb, stream, true) &&
408+
iter->containsStream(stream))
409+
{
410+
factor *= Optimizer::getSelectivity(*iter);
411+
}
412+
}
413+
414+
Optimizer::adjustSelectivity(selectivity, factor, streamCardinality);
415+
416+
// Don't consider external sorting if optimization for first rows is requested
417+
// and we have no local filtering applied
418+
419+
if (!optimizer->favorFirstRows() || selectivity < MAXIMUM_SELECTIVITY)
420+
{
421+
// Estimate amount of records to be sorted
422+
const auto cardinality = streamCardinality * selectivity;
423+
424+
// We optimistically assume that records will be cached during sorting
425+
const auto sortCost =
426+
// record copying (to the sort buffer and back)
427+
cardinality * COST_FACTOR_MEMCOPY * 2 +
428+
// quicksort algorithm is O(n*log(n)) in average
429+
cardinality * log2(cardinality) * COST_FACTOR_QUICKSORT;
430+
431+
// During navigation we fetch an index leaf page per every record being returned,
432+
// thus add the estimated cardinality to the cost
433+
auto navigationCost = navigationCandidate->cost +
434+
streamCardinality * candidate->selectivity;
435+
436+
if (optimizer->favorFirstRows())
437+
{
438+
// Reset the cost to represent a single record retrieval
439+
navigationCost = DEFAULT_INDEX_COST;
440+
441+
// We know that some local filtering is applied, so we need
442+
// to adjust the cost as we need to walk the index
443+
// until the first matching record is found
444+
const auto fullIndexCost = navigationCandidate->scratch->cardinality;
445+
const auto ratio = MAXIMUM_SELECTIVITY / selectivity;
446+
const auto fraction = ratio / streamCardinality;
447+
const auto walkCost = fullIndexCost * fraction * navigationCandidate->selectivity;
448+
navigationCost += walkCost;
449+
}
450+
451+
if (sortCost < navigationCost)
452+
return nullptr;
453+
}
454+
}
375455

376456
// Looks like we can do a navigational walk. Flag that
377457
// we have used this index for navigation, and allocate
378458
// a navigational rsb for it.
379459
scratch->index->idx_runtime_flags |= idx_navigate;
380460

381-
const USHORT key_length =
382-
ROUNDUP(BTR_key_length(tdbb, relation, scratch->index), sizeof(SLONG));
461+
const auto indexNode = makeIndexScanNode(scratch);
383462

384-
InversionNode* const index_node = makeIndexScanNode(scratch);
463+
const USHORT keyLength =
464+
ROUNDUP(BTR_key_length(tdbb, relation, scratch->index), sizeof(SLONG));
385465

386466
return FB_NEW_POOL(getPool())
387-
IndexTableScan(csb, getAlias(), stream, relation, index_node, key_length,
467+
IndexTableScan(csb, getAlias(), stream, relation, indexNode, keyLength,
388468
navigationCandidate->selectivity);
389469
}
390470

0 commit comments

Comments
 (0)