Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m13ui-0007RA-0P for pgsql-hackers@arkaria.postgresql.org; Wed, 07 Jul 2021 09:32:16 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1m13uf-0004QP-Sa for pgsql-hackers@arkaria.postgresql.org; Wed, 07 Jul 2021 09:32:13 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m13uf-0004QC-Fd for pgsql-hackers@lists.postgresql.org; Wed, 07 Jul 2021 09:32:13 +0000 Received: from mail-wm1-x331.google.com ([2a00:1450:4864:20::331]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1m13uc-0001O1-Vi for pgsql-hackers@lists.postgresql.org; Wed, 07 Jul 2021 09:32:12 +0000 Received: by mail-wm1-x331.google.com with SMTP id j39-20020a05600c1c27b029020028e48b8fso3731359wms.0 for ; Wed, 07 Jul 2021 02:32:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=aiven.io; s=google; h=from:to:cc:subject:date:message-id:organization:in-reply-to :references:mime-version:content-transfer-encoding; bh=RS+k3VwLRUn/40nuSpNL4wVm6Eb20D5qFwwdM9kgWS4=; b=bTXk2K+rjhRQK1tF3rOh0hOE75FYWKqrOrAKBk9/lvSot9r2Z2/q0v5PWzFUszE+uQ pCo4kmXL9s0XhRqSefeArnLcNe2yf7U+UDMz2+oNOlO+J/FA2KixLegML2zF9eRp2014 E9rSk5raymQiOJApjva+L83Eki2OtLcIbt1kk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:organization :in-reply-to:references:mime-version:content-transfer-encoding; bh=RS+k3VwLRUn/40nuSpNL4wVm6Eb20D5qFwwdM9kgWS4=; b=NYRFd01eSvN+LT38aAIKykFccSqirnqYuSqmIzqAprb1qCjY6RXKpILcaj5c9eig53 RR22ahkQzNQrJM4uTqpUXvaM4ZLUtf0e2A9SbgL/i81tDOTopKGmuzfmlDx9iVKW1lU2 a/gmtSvIlQ/iWDzyJrENqRUiHFERsYYFl1R18gncL9WjXW0QWO7Q9BkhaL+aDaFClim+ LWbYbYZf5tMpppoT7lCUPEsjDm0+8PC8aOJ4yehQAZMCCVf4zDy5eFXb8nw8GrQTb7xB d4j+5RdEgqedYKeKOWup4A2go7PvpeIptPpnYFI5lWjxhx3x69ExoRdHmK8XeAOixTpr dzHA== X-Gm-Message-State: AOAM531quVp2+j6SQyp0QmXz6z3lcMnhEmdgWlZ+Slt26WEuMoNtfC+N nzOD+5lz59O8cHL6Y/vB80W+1w== X-Google-Smtp-Source: ABdhPJwUVyxEMMKKF0t+gXdLQFk+GCVgdOTNevqVKXKrMN7aSbtfun6HNv0e2v2GhOanwAKO5Y3t5Q== X-Received: by 2002:a7b:c4ca:: with SMTP id g10mr5790804wmk.148.1625650329600; Wed, 07 Jul 2021 02:32:09 -0700 (PDT) Received: from aivenronan.localnet (static-176-158-121-96.ftth.abo.bbox.fr. [176.158.121.96]) by smtp.gmail.com with ESMTPSA id k15sm5629312wms.21.2021.07.07.02.32.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 07 Jul 2021 02:32:09 -0700 (PDT) From: Ronan Dunklau To: James Coleman Cc: PostgreSQL Developers , Ranier Vilela , David Rowley , Dilip Kumar Subject: Re: [PATCH] Use optimized single-datum tuplesort in ExecSort Date: Wed, 07 Jul 2021 11:32:03 +0200 Message-ID: <3060002.hb0XKQ11pn@aivenronan> Organization: aiven In-Reply-To: References: <3177670.itZtoPt7T5@aivenronan> <4385594.q5DUe7zQD7@aivenronan> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart3867834.kXIqkuDfFs" Content-Transfer-Encoding: 7Bit List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk This is a multi-part message in MIME format. --nextPart3867834.kXIqkuDfFs Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="iso-8859-1" Le mardi 6 juillet 2021, 17:37:53 CEST James Coleman a =E9crit : > Yes and no. When incremental sort has to do a full sort there will > always be at least 2 attributes. But in prefix sort mode (see > prefixsort_state) only non-presorted columns are sorted (i.e., if > given a,b already sorted by a, then only b is sorted). So the > prefixsort_state could use this optimization. The optimization is not when we actually sort on a single key, but when we = get=20 a single attribute in / out of the tuplesort. Since sorting always add=20 resjunk entries for the keys being sorted on, I don't think we can ever end= up=20 in a situation where the optimization would kick in, since the entries for = the=20 already-performed-sort keys will need to be present in the output. Maybe if instead of adding resjunk entries to the whole query's targetlist,= =20 sort and incrementalsort nodes were able to do a projection from the input= =20 (needed tle + resjunk sorting tle) to a tuple containing only the needed tl= e=20 on output before actually sorting it, it would be possible, but that would = be=20 quite a big design change. In the meantime I fixed some formatting issues, please find attached a new= =20 patch. =2D-=20 Ronan Dunklau --nextPart3867834.kXIqkuDfFs Content-Disposition: attachment; filename="v4-0001-Allow-Sort-nodes-to-use-the-fast-single-datum-tuples.patch" Content-Transfer-Encoding: 7Bit Content-Type: text/x-patch; charset="UTF-8"; name="v4-0001-Allow-Sort-nodes-to-use-the-fast-single-datum-tuples.patch" diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c index b99027e0d7..9d8b0a77da 100644 --- a/src/backend/executor/nodeSort.c +++ b/src/backend/executor/nodeSort.c @@ -29,6 +29,10 @@ * which saves the results in a temporary file or memory. After the * initial call, returns a tuple from the file with each call. * + * The tuplesort can either occur on the whole tuple (this is the nominal + * case) or, when the input / output tuple consists of only one attribute, + * we switch to the tuplesort_*_datum API, optimized for that specific case. + * * Conditions: * -- none. * @@ -86,32 +90,61 @@ ExecSort(PlanState *pstate) outerNode = outerPlanState(node); tupDesc = ExecGetResultType(outerNode); - tuplesortstate = tuplesort_begin_heap(tupDesc, - plannode->numCols, - plannode->sortColIdx, - plannode->sortOperators, - plannode->collations, - plannode->nullsFirst, - work_mem, - NULL, - node->randomAccess); + /* + * Switch to the tuplesort_*_datum interface when we have only one + * key, as it is much more efficient especially when the type is + * pass-by-value. + */ + if (tupDesc->natts == 1) + { + node->is_single_val = true; + tuplesortstate = tuplesort_begin_datum(TupleDescAttr(tupDesc, 0)->atttypid, + plannode->sortOperators[0], + plannode->collations[0], + plannode->nullsFirst[0], + work_mem, + NULL, + node->randomAccess); + } + else + tuplesortstate = tuplesort_begin_heap(tupDesc, + plannode->numCols, + plannode->sortColIdx, + plannode->sortOperators, + plannode->collations, + plannode->nullsFirst, + work_mem, + NULL, + node->randomAccess); if (node->bounded) tuplesort_set_bound(tuplesortstate, node->bound); node->tuplesortstate = (void *) tuplesortstate; /* - * Scan the subplan and feed all the tuples to tuplesort. + * Scan the subplan and feed all the tuples to tuplesort, using either + * the _putdatum or _puttupleslot API as appropriate. */ - - for (;;) - { - slot = ExecProcNode(outerNode); - - if (TupIsNull(slot)) - break; - - tuplesort_puttupleslot(tuplesortstate, slot); - } + if (node->is_single_val) + for (;;) + { + slot = ExecProcNode(outerNode); + + if (TupIsNull(slot)) + break; + slot_getsomeattrs(slot, 1); + tuplesort_putdatum(tuplesortstate, + slot->tts_values[0], + slot->tts_isnull[0]); + } + else + for (;;) + { + slot = ExecProcNode(outerNode); + + if (TupIsNull(slot)) + break; + tuplesort_puttupleslot(tuplesortstate, slot); + } /* * Complete the sort. @@ -150,9 +183,18 @@ ExecSort(PlanState *pstate) * next fetch from the tuplesort. */ slot = node->ss.ps.ps_ResultTupleSlot; - (void) tuplesort_gettupleslot(tuplesortstate, - ScanDirectionIsForward(dir), - false, slot, NULL); + if (node->is_single_val) + { + ExecClearTuple(slot); + if (tuplesort_getdatum(tuplesortstate, ScanDirectionIsForward(dir), + &(slot->tts_values[0]), &(slot->tts_isnull[0]), NULL)) + ExecStoreVirtualTuple(slot); + } + else + (void) tuplesort_gettupleslot(tuplesortstate, + ScanDirectionIsForward(dir), + false, slot, NULL); + return slot; } @@ -191,6 +233,7 @@ ExecInitSort(Sort *node, EState *estate, int eflags) sortstate->bounded = false; sortstate->sort_Done = false; sortstate->tuplesortstate = NULL; + sortstate->is_single_val = false; /* * Miscellaneous initialization diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index 22972071ff..f785259da7 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -4773,6 +4773,14 @@ leader_takeover_tapes(Tuplesortstate *state) static void free_sort_tuple(Tuplesortstate *state, SortTuple *stup) { - FREEMEM(state, GetMemoryChunkSpace(stup->tuple)); - pfree(stup->tuple); + /* + * If the SortTuple is actually only a single Datum, which was not copied + * as it is a byval type, do not try to free it nor account for it in + * memory used. + */ + if (stup->tuple) + { + FREEMEM(state, GetMemoryChunkSpace(stup->tuple)); + pfree(stup->tuple); + } } diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 0ec5509e7e..643f416c54 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2151,6 +2151,7 @@ typedef struct SortState int64 bound_Done; /* value of bound we did the sort with */ void *tuplesortstate; /* private state of tuplesort.c */ bool am_worker; /* are we a worker? */ + bool is_single_val; /* are we using the single value optimization ? */ SharedSortInfo *shared_info; /* one entry per worker */ } SortState; -- 2.32.0 --nextPart3867834.kXIqkuDfFs--