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 1m0mbx-00063D-Fa for pgsql-hackers@arkaria.postgresql.org; Tue, 06 Jul 2021 15:03:45 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1m0mbw-00082Z-7P for pgsql-hackers@arkaria.postgresql.org; Tue, 06 Jul 2021 15:03:44 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1m0mbv-00082R-Si for pgsql-hackers@lists.postgresql.org; Tue, 06 Jul 2021 15:03:43 +0000 Received: from mail-wm1-x331.google.com ([2a00:1450:4864:20::331]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1m0mbs-0007j4-VH for pgsql-hackers@lists.postgresql.org; Tue, 06 Jul 2021 15:03:43 +0000 Received: by mail-wm1-x331.google.com with SMTP id h18-20020a05600c3512b029020e4ceb9588so1988278wmq.5 for ; Tue, 06 Jul 2021 08:03:40 -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=419jBLsOV7uk6G8QiaBurfLsqUzSJdPCArel4nnlYvQ=; b=fIc9zAWa0Xr6BpCygoAebrMncRb/Awq3xsFyG7h8Df+KG1ctlOQe7MkkytuC7y7VaX I4vFLjrpb1SbuJUAPur7HS9xnpTOcr4E7Pm6AQMvuo9nKON9EOJz0H6MAcAPfkKlBds2 obbdEUjLwa0bC5RBYDlAGlIbXYSGV6u9ZYm7o= 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=419jBLsOV7uk6G8QiaBurfLsqUzSJdPCArel4nnlYvQ=; b=RpLfrsRlhCgMVnDY6gE4sLoRK9q2r8hbM55TocQ2zCyXJqbbdTdd/Hhfjmab6upD5y p86ZDqDYaqy/XylxIbQF8VCtdFhGuX3VHNSUZT7maYqwUMuTK1/xMdFBRszdROzMtbTf G41bYS4nEhvhtWxsu1eHAf4pF6NVotTE0MM1UPkdn/DRNaYJy19hzrIAgBJBZxSMzaPI /O/fJghJHL+k329iku8m9XmoXgU5fIrFmtgxS1gPy22KleQ5u7HKOwOglxJvZ2qeUpfq WmLZtscOzaAmQfBpayTqtp1DGxo6yJYToVqRc9T2EG1mU2AtrydizaWI/R18W6nKq0yb Dkgw== X-Gm-Message-State: AOAM530YJDdX+LgAl/BO2ge3MQVHo6Ouwz5wBm4WLRBzw3ti9v0hI94Y a3XLK0vRAwxTsoc+Up1JqsEFFw== X-Google-Smtp-Source: ABdhPJwz5czLKCxh9Ab0/hySV5Ql0W5IK6EE4oTkbniqwk1DvbyTlI4ywGjPcKnWbXJX1VZ7Obrz5g== X-Received: by 2002:a1c:dd07:: with SMTP id u7mr21339831wmg.47.1625583818930; Tue, 06 Jul 2021 08:03:38 -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 v2sm16615552wru.16.2021.07.06.08.03.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 06 Jul 2021 08:03:38 -0700 (PDT) From: Ronan Dunklau To: James Coleman , pgsql-hackers@lists.postgresql.org, Ranier Vilela Cc: David Rowley , Dilip Kumar Subject: Re: [PATCH] Use optimized single-datum tuplesort in ExecSort Date: Tue, 06 Jul 2021 17:03:32 +0200 Message-ID: <4385594.q5DUe7zQD7@aivenronan> Organization: aiven In-Reply-To: References: <3177670.itZtoPt7T5@aivenronan> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart7348946.joQKHNXfS7" 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. --nextPart7348946.joQKHNXfS7 Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="us-ascii" Thank you for the review, I will address those shortly, but will answer some questions in the meantime. > > First, the changes are lacking any explanatory comments. Probably we > > should follow how nodeAgg does this and add both comments to the > > ExecSort function header as well as specific comments above the "if" > > around the new tuplesort_begin_datum explaining the specific > > conditions that are required for the optimization to be useful and > > safe. Done, since I lifted the restrictions following your questions, there isn't much left to comment. (see below) > > > > That leads to a question I had: I don't follow why bounded mode (when > > using byval) needs to be excluded. Comments should be added if there's > > a good reason (as noted above), but maybe it's a case we can handle > > safely? I had test failures when trying to move the Datum around when performing a bounded sort, but did not look into it at first. Now I've looked into it, and the switch to a heapsort when using bounded mode just unconditionnaly tried to free a tuple that was never there to begin with. So if the SortTuple does not contain an actual tuple, but only a single datum, do not do that. I've updated the patch to fix this and enable the optimization in the case of bounded sort. > > > > A second question: at first glance it's intuitively the case we might > > not be able to handle byref values. But nodeAgg doesn't seem to have > > that restriction. What's the difference here? > > I think tuplesort_begin_datum, doesn't have any such limitation, it > can handle any type of Datum so I think we don't need to consider the > only attbyval, we can consider any type of attribute for this > optimization. I've restricted the optimization to byval types because of the following comment in nodeAgg.c: /* * Note: if input type is pass-by-ref, the datums returned by the sort are * freshly palloc'd in the per-query context, so we must be careful to * pfree them when they are no longer needed. */ As I was not sure how to handle that, I prefered the safety of not enabling it. Since you both told me it should be safe, I've lifted that restriction too. > A few small code observations: > - In my view the addition of unlikely() in ExecSort is unlikely to be > of benefit because it's a single call for the entire node's execution > (not in the tuple loop). Done. > - It seems clearer to change the "if (!node->is_single_val)" to flip > the true/false cases so we don't need the negation. Agreed, done. > - I assume there are tests that likely already cover this case, but > it'd be worth verifying that. Yes many test cases cover that, but maybe it would be better to explictly check for it on some cases: do you think we could add a debug message that can be checked for ? > Finally, I believe the same optimization likely ought to be added to > nodeIncrementalSort. It's less likely the tests there are sufficient > for both this and the original case, but we'll see. I will look into it, but isn't incrementalsort used to sort tuples on several keys, when they are already sorted on the first ? In that case, I doubt we would ever have a single-valued tuple here, except if there is a projection to strip the tuple from extraneous attributes. -- Ronan Dunklau --nextPart7348946.joQKHNXfS7 Content-Disposition: attachment; filename="v3-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="v3-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..ff443d15a9 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. * @@ -85,33 +89,59 @@ 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 +180,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 +230,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..226a723167 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -4773,6 +4773,13 @@ 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; --nextPart7348946.joQKHNXfS7--