Skip to content

Commit 91e9e89

Browse files
committed
Make nodeSort.c use Datum sorts for single column sorts
Datum sorts can be significantly faster than tuple sorts, especially when the data type being sorted is a pass-by-value type. Something in the region of 50-70% performance improvements appear to be possible. Just in case there's any confusion; the Datum sort is only used when the targetlist of the Sort node contains a single column, not when there's a single column in the sort key and multiple items in the target list. Author: Ronan Dunklau Reviewed-by: James Coleman, David Rowley, Ranier Vilela, Hou Zhijie Tested-by: John Naylor Discussion: https://postgr.es/m/3177670.itZtoPt7T5@aivenronan
1 parent 7fa1e1e commit 91e9e89

File tree

2 files changed

+82
-25
lines changed

2 files changed

+82
-25
lines changed

src/backend/executor/nodeSort.c

Lines changed: 81 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@
2929
* which saves the results in a temporary file or memory. After the
3030
* initial call, returns a tuple from the file with each call.
3131
*
32+
* There are two distinct ways that this sort can be performed:
33+
*
34+
* 1) When the result is a single column we perform a Datum sort.
35+
*
36+
* 2) When the result contains multiple columns we perform a tuple sort.
37+
*
38+
* We could do this by always performing a tuple sort, however sorting
39+
* Datums only can be significantly faster than sorting tuples,
40+
* especially when the Datums are of a pass-by-value type.
41+
*
3242
* Conditions:
3343
* -- none.
3444
*
@@ -86,31 +96,56 @@ ExecSort(PlanState *pstate)
8696
outerNode = outerPlanState(node);
8797
tupDesc = ExecGetResultType(outerNode);
8898

89-
tuplesortstate = tuplesort_begin_heap(tupDesc,
90-
plannode->numCols,
91-
plannode->sortColIdx,
92-
plannode->sortOperators,
93-
plannode->collations,
94-
plannode->nullsFirst,
95-
work_mem,
96-
NULL,
97-
node->randomAccess);
99+
if (node->datumSort)
100+
tuplesortstate = tuplesort_begin_datum(TupleDescAttr(tupDesc, 0)->atttypid,
101+
plannode->sortOperators[0],
102+
plannode->collations[0],
103+
plannode->nullsFirst[0],
104+
work_mem,
105+
NULL,
106+
node->randomAccess);
107+
else
108+
tuplesortstate = tuplesort_begin_heap(tupDesc,
109+
plannode->numCols,
110+
plannode->sortColIdx,
111+
plannode->sortOperators,
112+
plannode->collations,
113+
plannode->nullsFirst,
114+
work_mem,
115+
NULL,
116+
node->randomAccess);
98117
if (node->bounded)
99118
tuplesort_set_bound(tuplesortstate, node->bound);
100119
node->tuplesortstate = (void *) tuplesortstate;
101120

102121
/*
103-
* Scan the subplan and feed all the tuples to tuplesort.
122+
* Scan the subplan and feed all the tuples to tuplesort using the
123+
* appropriate method based on the type of sort we're doing.
104124
*/
105-
106-
for (;;)
125+
if (node->datumSort)
107126
{
108-
slot = ExecProcNode(outerNode);
109-
110-
if (TupIsNull(slot))
111-
break;
112-
113-
tuplesort_puttupleslot(tuplesortstate, slot);
127+
for (;;)
128+
{
129+
slot = ExecProcNode(outerNode);
130+
131+
if (TupIsNull(slot))
132+
break;
133+
slot_getsomeattrs(slot, 1);
134+
tuplesort_putdatum(tuplesortstate,
135+
slot->tts_values[0],
136+
slot->tts_isnull[0]);
137+
}
138+
}
139+
else
140+
{
141+
for (;;)
142+
{
143+
slot = ExecProcNode(outerNode);
144+
145+
if (TupIsNull(slot))
146+
break;
147+
tuplesort_puttupleslot(tuplesortstate, slot);
148+
}
114149
}
115150

116151
/*
@@ -144,15 +179,27 @@ ExecSort(PlanState *pstate)
144179
SO1_printf("ExecSort: %s\n",
145180
"retrieving tuple from tuplesort");
146181

182+
slot = node->ss.ps.ps_ResultTupleSlot;
183+
147184
/*
148-
* Get the first or next tuple from tuplesort. Returns NULL if no more
149-
* tuples. Note that we only rely on slot tuple remaining valid until the
150-
* next fetch from the tuplesort.
185+
* Fetch the next sorted item from the appropriate tuplesort function. For
186+
* datum sorts we must manage the slot ourselves and leave it clear when
187+
* tuplesort_getdatum returns false to indicate there are no more datums.
188+
* For tuple sorts, tuplesort_gettupleslot manages the slot for us and
189+
* empties the slot when it runs out of tuples.
151190
*/
152-
slot = node->ss.ps.ps_ResultTupleSlot;
153-
(void) tuplesort_gettupleslot(tuplesortstate,
154-
ScanDirectionIsForward(dir),
155-
false, slot, NULL);
191+
if (node->datumSort)
192+
{
193+
ExecClearTuple(slot);
194+
if (tuplesort_getdatum(tuplesortstate, ScanDirectionIsForward(dir),
195+
&(slot->tts_values[0]), &(slot->tts_isnull[0]), NULL))
196+
ExecStoreVirtualTuple(slot);
197+
}
198+
else
199+
(void) tuplesort_gettupleslot(tuplesortstate,
200+
ScanDirectionIsForward(dir),
201+
false, slot, NULL);
202+
156203
return slot;
157204
}
158205

@@ -221,6 +268,15 @@ ExecInitSort(Sort *node, EState *estate, int eflags)
221268
ExecInitResultTupleSlotTL(&sortstate->ss.ps, &TTSOpsMinimalTuple);
222269
sortstate->ss.ps.ps_ProjInfo = NULL;
223270

271+
/*
272+
* We perform a Datum sort when we're sorting just a single column,
273+
* otherwise we perform a tuple sort.
274+
*/
275+
if (ExecGetResultType(outerPlanState(sortstate))->natts == 1)
276+
sortstate->datumSort = true;
277+
else
278+
sortstate->datumSort = false;
279+
224280
SO1_printf("ExecInitSort: %s\n",
225281
"sort node initialized");
226282

src/include/nodes/execnodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,7 @@ typedef struct SortState
21512151
int64 bound_Done; /* value of bound we did the sort with */
21522152
void *tuplesortstate; /* private state of tuplesort.c */
21532153
bool am_worker; /* are we a worker? */
2154+
bool datumSort; /* Datum sort instead of tuple sort? */
21542155
SharedSortInfo *shared_info; /* one entry per worker */
21552156
} SortState;
21562157

0 commit comments

Comments
 (0)