v6-1-add-Sort-ndistInFirstRow.patch
application/octet-stream
Filename: v6-1-add-Sort-ndistInFirstRow.patch
Type: application/octet-stream
Part: 4
Patch
Format: format-patch
Series: patch v6-0002
Subject: add Sort->ndistInFirstRow
| File | + | − |
|---|---|---|
| src/backend/executor/nodeSort.c | 2 | 0 |
| src/backend/optimizer/plan/createplan.c | 202 | 30 |
| src/backend/utils/sort/mk_qsort_tuple.c | 4 | 0 |
| src/backend/utils/sort/tuplesort.c | 14 | 1 |
| src/include/nodes/plannodes.h | 3 | 0 |
| src/include/utils/tuplesort.h | 2 | 0 |
From c4eb10fecd7e517746f77154c671bb6fb6ff9500 Mon Sep 17 00:00:00 2001
From: Yao Wang <yaowangm@outlook.com>
Date: Tue, 9 Jul 2024 08:05:43 +0000
Subject: [PATCH 2/2] add Sort->ndistInFirstRow
---
src/backend/executor/nodeSort.c | 2 +
src/backend/optimizer/plan/createplan.c | 232 +++++++++++++++++++++---
src/backend/utils/sort/mk_qsort_tuple.c | 4 +
src/backend/utils/sort/tuplesort.c | 15 +-
src/include/nodes/plannodes.h | 3 +
src/include/utils/tuplesort.h | 2 +
6 files changed, 227 insertions(+), 31 deletions(-)
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 3fc925d7b4..c8e85568eb 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -122,6 +122,8 @@ ExecSort(PlanState *pstate)
tuplesortopts);
if (node->bounded)
tuplesort_set_bound(tuplesortstate, node->bound);
+ tuplesort_set_mkqsApplicable(tuplesortstate,
+ plannode->mkqsApplicable);
node->tuplesortstate = (void *) tuplesortstate;
/*
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 6b64c4a362..1e17939f71 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -42,6 +42,8 @@
#include "parser/parsetree.h"
#include "partitioning/partprune.h"
#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "catalog/pg_statistic.h"
/*
@@ -256,12 +258,14 @@ static MergeJoin *make_mergejoin(List *tlist,
bool skip_mark_restore);
static Sort *make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
- Oid *collations, bool *nullsFirst);
+ Oid *collations, bool *nullsFirst,
+ bool mkqsApplicable);
static IncrementalSort *make_incrementalsort(Plan *lefttree,
int numCols, int nPresortedCols,
AttrNumber *sortColIdx, Oid *sortOperators,
Oid *collations, bool *nullsFirst);
-static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+static Plan *prepare_sort_from_pathkeys(PlannerInfo *root,
+ Plan *lefttree, List *pathkeys,
Relids relids,
const AttrNumber *reqColIdx,
bool adjust_tlist_in_place,
@@ -269,8 +273,9 @@ static Plan *prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
- bool **p_nullsFirst);
-static Sort *make_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+ bool **p_nullsFirst,
+ bool *mkqsApplicable);
+static Sort *make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
Relids relids);
static IncrementalSort *make_incrementalsort_from_pathkeys(Plan *lefttree,
List *pathkeys, Relids relids, int nPresortedCols);
@@ -1282,7 +1287,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
* function result; it must be the same plan node. However, we then
* need to detect whether any tlist entries were added.
*/
- (void) prepare_sort_from_pathkeys((Plan *) plan, pathkeys,
+ (void) prepare_sort_from_pathkeys(NULL,
+ (Plan *) plan, pathkeys,
best_path->path.parent->relids,
NULL,
true,
@@ -1290,7 +1296,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
&nodeSortColIdx,
&nodeSortOperators,
&nodeCollations,
- &nodeNullsFirst);
+ &nodeNullsFirst,
+ NULL);
tlist_was_changed = (orig_tlist_length != list_length(plan->plan.targetlist));
}
@@ -1326,7 +1333,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
* don't need an explicit sort, to make sure they are returning
* the same sort key columns the Append expects.
*/
- subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+ subplan = prepare_sort_from_pathkeys(NULL,
+ subplan, pathkeys,
subpath->parent->relids,
nodeSortColIdx,
false,
@@ -1334,7 +1342,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ NULL);
/*
* Check that we got the same sort key information. We just
@@ -1358,7 +1367,8 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
{
Sort *sort = make_sort(subplan, numsortkeys,
sortColIdx, sortOperators,
- collations, nullsFirst);
+ collations, nullsFirst,
+ NULL);
label_sort_with_costsize(root, sort, best_path->limit_tuples);
subplan = (Plan *) sort;
@@ -1467,7 +1477,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
* function result; it must be the same plan node. However, we then need
* to detect whether any tlist entries were added.
*/
- (void) prepare_sort_from_pathkeys(plan, pathkeys,
+ (void) prepare_sort_from_pathkeys(NULL,
+ plan, pathkeys,
best_path->path.parent->relids,
NULL,
true,
@@ -1475,7 +1486,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
&node->sortColIdx,
&node->sortOperators,
&node->collations,
- &node->nullsFirst);
+ &node->nullsFirst,
+ NULL);
tlist_was_changed = (orig_tlist_length != list_length(plan->targetlist));
/*
@@ -1498,7 +1510,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
/* Compute sort column info, and adjust subplan's tlist as needed */
- subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+ subplan = prepare_sort_from_pathkeys(NULL,
+ subplan, pathkeys,
subpath->parent->relids,
node->sortColIdx,
false,
@@ -1506,7 +1519,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ NULL);
/*
* Check that we got the same sort key information. We just Assert
@@ -1530,7 +1544,8 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path,
{
Sort *sort = make_sort(subplan, numsortkeys,
sortColIdx, sortOperators,
- collations, nullsFirst);
+ collations, nullsFirst,
+ NULL);
label_sort_with_costsize(root, sort, best_path->limit_tuples);
subplan = (Plan *) sort;
@@ -1977,7 +1992,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
Assert(pathkeys != NIL);
/* Compute sort column info, and adjust subplan's tlist as needed */
- subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
+ subplan = prepare_sort_from_pathkeys(NULL,
+ subplan, pathkeys,
best_path->subpath->parent->relids,
gm_plan->sortColIdx,
false,
@@ -1985,7 +2001,8 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
&gm_plan->sortColIdx,
&gm_plan->sortOperators,
&gm_plan->collations,
- &gm_plan->nullsFirst);
+ &gm_plan->nullsFirst,
+ NULL);
/*
@@ -2196,7 +2213,7 @@ create_sort_plan(PlannerInfo *root, SortPath *best_path, int flags)
* relids. Thus, if this sort path is based on a child relation, we must
* pass its relids.
*/
- plan = make_sort_from_pathkeys(subplan, best_path->path.pathkeys,
+ plan = make_sort_from_pathkeys(root, subplan, best_path->path.pathkeys,
IS_OTHER_REL(best_path->subpath->parent) ?
best_path->path.parent->relids : NULL);
@@ -4526,7 +4543,7 @@ create_mergejoin_plan(PlannerInfo *root,
if (best_path->outersortkeys)
{
Relids outer_relids = outer_path->parent->relids;
- Sort *sort = make_sort_from_pathkeys(outer_plan,
+ Sort *sort = make_sort_from_pathkeys(NULL, outer_plan,
best_path->outersortkeys,
outer_relids);
@@ -4540,7 +4557,7 @@ create_mergejoin_plan(PlannerInfo *root,
if (best_path->innersortkeys)
{
Relids inner_relids = inner_path->parent->relids;
- Sort *sort = make_sort_from_pathkeys(inner_plan,
+ Sort *sort = make_sort_from_pathkeys(NULL, inner_plan,
best_path->innersortkeys,
inner_relids);
@@ -6067,7 +6084,8 @@ make_mergejoin(List *tlist,
static Sort *
make_sort(Plan *lefttree, int numCols,
AttrNumber *sortColIdx, Oid *sortOperators,
- Oid *collations, bool *nullsFirst)
+ Oid *collations, bool *nullsFirst,
+ bool mkqsApplicable)
{
Sort *node;
Plan *plan;
@@ -6084,6 +6102,7 @@ make_sort(Plan *lefttree, int numCols,
node->sortOperators = sortOperators;
node->collations = collations;
node->nullsFirst = nullsFirst;
+ node->mkqsApplicable = mkqsApplicable;
return node;
}
@@ -6161,7 +6180,8 @@ make_incrementalsort(Plan *lefttree, int numCols, int nPresortedCols,
* or a Result stacked atop lefttree).
*/
static Plan *
-prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
+prepare_sort_from_pathkeys(PlannerInfo *root,
+ Plan *lefttree, List *pathkeys,
Relids relids,
const AttrNumber *reqColIdx,
bool adjust_tlist_in_place,
@@ -6169,7 +6189,8 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
AttrNumber **p_sortColIdx,
Oid **p_sortOperators,
Oid **p_collations,
- bool **p_nullsFirst)
+ bool **p_nullsFirst,
+ bool *mkqsApplicable)
{
List *tlist = lefttree->targetlist;
ListCell *i;
@@ -6179,6 +6200,33 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Oid *collations;
bool *nullsFirst;
+ /* Calculation of benefit from multi-key qsort
+ *
+ * The algorithm is:
+ *
+ * mkqsBene = (sum of benefit of each sort key) - fixed cost
+ * (sum of benefit of each sort key) =
+ * weight * (duplicated value ratio of previous sort key)
+ *
+ * We get the duplicated value ratio from catalog table (pg_statistic.
+ * stadistinct), which is not always correct.
+ * In addition, the value might be unavailable sometimes because the sort
+ * key is not from a real table or the query to catalog table fails. For
+ * the case, we simply ignore the sort key for calcaulation.
+ *
+ * Enable mk qsort only when estimated benefit >= 0.05, which means
+ * "mk qsort is supposed to be 5% faster than classical qsort".
+ */
+
+ /* Set mkqsBene to -0.02 to indicate the fixed cost */
+ double mkqsBene = -0.02;
+
+ /*
+ * Duplicated value ratio of previous sort key, init to 1.0 for first sort
+ * key to indicate "count all value"
+ */
+ double dupRio = 1.0;
+
/*
* We will need at most list_length(pathkeys) sort columns; possibly less
*/
@@ -6188,6 +6236,9 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
collations = (Oid *) palloc(numsortkeys * sizeof(Oid));
nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
+ if (mkqsApplicable)
+ *mkqsApplicable = false;
+
numsortkeys = 0;
foreach(i, pathkeys)
@@ -6200,6 +6251,9 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
Oid sortop;
ListCell *j;
+ /* Init value of ndist to -1 to indicate "unknown" */
+ double ndist = -1;
+
if (ec->ec_has_volatile)
{
/*
@@ -6303,6 +6357,54 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
lefttree->targetlist = tlist; /* just in case NIL before */
}
+ /* Get the ndist value from pg_stats */
+ if (mkqsApplicable && root != NULL)
+ {
+ /*
+ * Calculate ndist only when SORT is based on a real table
+ */
+ if (IsA(tle->expr, Var))
+ {
+ Var *var = (Var *)tle->expr;
+ RangeTblEntry *rte = root->simple_rte_array[var->varno];
+
+ /*
+ * Try to get distinct info from pg_statistic
+ */
+
+ if (rte)
+ {
+ HeapTuple tuple = SearchSysCache3(
+ STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(false));
+ if (HeapTupleIsValid(tuple))
+ {
+ /* Use the pg_statistic entry */
+ Form_pg_statistic stats;
+
+ stats = (Form_pg_statistic) GETSTRUCT(tuple);
+ /*
+ * If stats->stadistinct < 0, it means a fraction of
+ * distinct tuple ratio; if it > 0, it means the number
+ * of distinct tuples; 0 means "unknown".
+ */
+ if (stats->stadistinct < 0)
+ ndist = -stats->stadistinct;
+ else
+ {
+ RelOptInfo *rel = root->simple_rel_array[var->varno];
+ ndist = stats->stadistinct / rel->tuples;
+ }
+ }
+
+ if (tuple)
+ ReleaseSysCache(tuple);
+ }
+ }
+ }
+
/*
* Look up the correct sort operator from the PathKey's slightly
* abstracted representation.
@@ -6321,6 +6423,61 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
sortOperators[numsortkeys] = sortop;
collations[numsortkeys] = ec->ec_collation;
nullsFirst[numsortkeys] = pathkey->pk_nulls_first;
+
+ /*
+ * If ndist is valid, calculate benefit for mk qsort
+ */
+ if (ndist != -1)
+ {
+ double bene = 0;
+ SortSupportData sortKey;
+
+ sortKey.comparator = NULL;
+ sortKey.ssup_cxt = CurrentMemoryContext;
+ sortKey.ssup_collation = collations[numsortkeys];
+ sortKey.ssup_nulls_first = nullsFirst[numsortkeys];
+ sortKey.ssup_attno = sortColIdx[numsortkeys];
+ PrepareSortSupportFromOrderingOp(sortop, &sortKey);
+
+ /*
+ * For data type with/without specialized comparator, use different
+ * weights. The weights are determined by some experiments and may
+ * be not accurate for all possible cases.
+ */
+ if (sortKey.comparator == ssup_datum_unsigned_cmp ||
+#if SIZEOF_DATUM >= 8
+ sortKey.comparator == ssup_datum_signed_cmp ||
+#endif
+ sortKey.comparator == ssup_datum_int32_cmp)
+ {
+ /*
+ * For data type with specialized comparator, ignore the first
+ * sort key because there is no benefit from duplciated values
+ * for first sort key.
+ */
+ if (numsortkeys > 0)
+ bene += dupRio * 0.05;
+ }
+ else
+ {
+ /*
+ * For data type without specialized comparator, mksort is
+ * faster than classical qsort even there is no duplicate. So
+ * add 1 to dupRio for the extra benefit from no-duplicate
+ * values.
+ */
+ bene += (dupRio + 1) * 0.05;
+ }
+ mkqsBene += bene;
+ }
+
+ /*
+ * Remember duplicated value ratio of current sort key for
+ * calculation by next sort key
+ */
+ if (ndist != -1)
+ dupRio *= (1 - ndist);
+
numsortkeys++;
}
@@ -6331,6 +6488,13 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
*p_collations = collations;
*p_nullsFirst = nullsFirst;
+ /* Enable mk qsort only when estimated benefit >= 0.05 */
+ if (mkqsBene >= 0.05)
+ {
+ Assert(mkqsApplicable);
+ *mkqsApplicable = true;
+ }
+
return lefttree;
}
@@ -6343,16 +6507,19 @@ prepare_sort_from_pathkeys(Plan *lefttree, List *pathkeys,
* 'relids' is the set of relations required by prepare_sort_from_pathkeys()
*/
static Sort *
-make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
+make_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree,
+ List *pathkeys, Relids relids)
{
int numsortkeys;
AttrNumber *sortColIdx;
Oid *sortOperators;
Oid *collations;
bool *nullsFirst;
+ bool mkqsApplicable;
/* Compute sort column info, and adjust lefttree as needed */
- lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+ lefttree = prepare_sort_from_pathkeys(root,
+ lefttree, pathkeys,
relids,
NULL,
false,
@@ -6360,12 +6527,13 @@ make_sort_from_pathkeys(Plan *lefttree, List *pathkeys, Relids relids)
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ &mkqsApplicable);
/* Now build the Sort node */
return make_sort(lefttree, numsortkeys,
sortColIdx, sortOperators,
- collations, nullsFirst);
+ collations, nullsFirst, mkqsApplicable);
}
/*
@@ -6388,7 +6556,8 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
bool *nullsFirst;
/* Compute sort column info, and adjust lefttree as needed */
- lefttree = prepare_sort_from_pathkeys(lefttree, pathkeys,
+ lefttree = prepare_sort_from_pathkeys(NULL,
+ lefttree, pathkeys,
relids,
NULL,
false,
@@ -6396,7 +6565,8 @@ make_incrementalsort_from_pathkeys(Plan *lefttree, List *pathkeys,
&sortColIdx,
&sortOperators,
&collations,
- &nullsFirst);
+ &nullsFirst,
+ NULL);
/* Now build the Sort node */
return make_incrementalsort(lefttree, numsortkeys, nPresortedCols,
@@ -6444,7 +6614,8 @@ make_sort_from_sortclauses(List *sortcls, Plan *lefttree)
return make_sort(lefttree, numsortkeys,
sortColIdx, sortOperators,
- collations, nullsFirst);
+ collations, nullsFirst,
+ NULL);
}
/*
@@ -6498,7 +6669,8 @@ make_sort_from_groupcols(List *groupcls,
return make_sort(lefttree, numsortkeys,
sortColIdx, sortOperators,
- collations, nullsFirst);
+ collations, nullsFirst,
+ NULL);
}
static Material *
diff --git a/src/backend/utils/sort/mk_qsort_tuple.c b/src/backend/utils/sort/mk_qsort_tuple.c
index f8588df54b..cb6f409bdd 100644
--- a/src/backend/utils/sort/mk_qsort_tuple.c
+++ b/src/backend/utils/sort/mk_qsort_tuple.c
@@ -163,12 +163,14 @@ mkqs_compare_datum_by_shortcut(SortTuple *tuple1,
tuple2->datum1,
tuple2->isnull1,
sortKey);
+#if SIZEOF_DATUM >= 8
else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
ret = ApplySignedSortComparator(tuple1->datum1,
tuple1->isnull1,
tuple2->datum1,
tuple2->isnull1,
sortKey);
+#endif
else if (compFuncType == MKQS_COMP_FUNC_INT32)
ret = ApplyInt32SortComparator(tuple1->datum1,
tuple1->isnull1,
@@ -387,8 +389,10 @@ mkqs_compare_tuple(SortTuple *a, SortTuple *b, Tuplesortstate *state)
if (compFuncType == MKQS_COMP_FUNC_UNSIGNED)
ret = qsort_tuple_unsigned_compare(a, b, state);
+#if SIZEOF_DATUM >= 8
else if (compFuncType == MKQS_COMP_FUNC_SIGNED)
ret = qsort_tuple_signed_compare(a, b, state);
+#endif
else if (compFuncType == MKQS_COMP_FUNC_INT32)
ret = qsort_tuple_int32_compare(a, b, state);
else
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 6dd21a2710..ea55113dc3 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -341,6 +341,9 @@ struct Tuplesortstate
/* Whether multi-key quick sort is used */
bool mkqsUsed;
+
+ /* Should multi-key quick be used or not? Determined by optimizer */
+ bool mkqsApplicable;
};
/*
@@ -2736,6 +2739,9 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
* type is supported by mk qsort. (By now only Heap tuple and Btree
* Index tuple are supported, and more types may be supported in
* future.)
+ * 4. state->mkqsApplicable is true, which is determined by optimizer
+ * and means "mk qsort is supposed to be faster than qsort" for
+ * current case.
*
* A summary of tuple types supported by mk qsort:
*
@@ -2749,7 +2755,8 @@ tuplesort_sort_memtuples(Tuplesortstate *state)
*/
if (enable_mk_sort &&
state->base.nKeys > 1 &&
- state->base.mkqsGetDatumFunc != NULL)
+ state->base.mkqsGetDatumFunc != NULL &&
+ state->mkqsApplicable)
{
/*
* Set relevant Datum Sort Comparator according to concrete data type
@@ -3275,3 +3282,9 @@ ssup_datum_int32_cmp(Datum x, Datum y, SortSupport ssup)
else
return 0;
}
+
+void tuplesort_set_mkqsApplicable(Tuplesortstate *state,
+ bool mkqsApplicable)
+{
+ state->mkqsApplicable = mkqsApplicable;
+}
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index e025679f89..2e1c22701a 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -946,6 +946,9 @@ typedef struct Sort
/* NULLS FIRST/LAST directions */
bool *nullsFirst pg_node_attr(array_size(numCols));
+
+ /* Should multi-key quick be used or not? Determined by optimizer */
+ bool mkqsApplicable;
} Sort;
/* ----------------
diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h
index 60eb77ee01..cfcfdd6354 100644
--- a/src/include/utils/tuplesort.h
+++ b/src/include/utils/tuplesort.h
@@ -507,6 +507,8 @@ extern BrinTuple *tuplesort_getbrintuple(Tuplesortstate *state, Size *len,
bool forward);
extern bool tuplesort_getdatum(Tuplesortstate *state, bool forward, bool copy,
Datum *val, bool *isNull, Datum *abbrev);
+extern void tuplesort_set_mkqsApplicable(Tuplesortstate *state,
+ bool mkqsApplicable);
#endif /* TUPLESORT_H */
--
2.25.1