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 1m0eN8-0005Tv-AZ for pgsql-hackers@arkaria.postgresql.org; Tue, 06 Jul 2021 06:15:54 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1m0eN7-0002Jq-AO for pgsql-hackers@arkaria.postgresql.org; Tue, 06 Jul 2021 06:15:53 +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 1m0eN7-0002Ig-0K for pgsql-hackers@lists.postgresql.org; Tue, 06 Jul 2021 06:15:53 +0000 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]) by magus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1m0eN2-00034V-QD for pgsql-hackers@lists.postgresql.org; Tue, 06 Jul 2021 06:15:52 +0000 Received: by mail-wm1-x32f.google.com with SMTP id k16-20020a05600c1c90b02901f4ed0fcfe7so1377190wms.5 for ; Mon, 05 Jul 2021 23:15:48 -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:mime-version :content-transfer-encoding; bh=ZD9IN1bwAAshrGrQxUP/A602v1CQGHBxZXvgzJQqGmk=; b=QkKW6shry7NhuGOyhc5445WreISo9PsVCZ5DqJwGNOcIImyzV2jAiD5JGHCmcCtvIY rvFHs25Zt09YcJKYhObIJVFVuBellXonKqz3HhO7JJpDQtCxh5h1l3UN3ERgoaQarUzs dW+CPR8rUEfrWWyQFPkn4fntxV8z0q9sdAI+o= 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 :mime-version:content-transfer-encoding; bh=ZD9IN1bwAAshrGrQxUP/A602v1CQGHBxZXvgzJQqGmk=; b=GUpaW+lD6qcY1ELQKIYIshPjq2Nqbk/TB21TDeA1zgAx8tXbdDqu8Q/GVHAtgGBtRb l9sVwz2skN+keXmF4h8w0kpQufeFyso8vJ05NonVQmSfUN0zmVqRCBTJR6ZtR8NKMVvr eA9cVx7uw3q4/fhxrAsiv4I7A97BxR/30SaOHqJsdui3sH1oocCbvfdAg/ZtKikCF3CS 2jvKC4bVDGsvVoySnL0G+oX/meLnDRc2NWsWPHAbYiwNFPt4A6cd9e60RVSzBW7AxEB4 198A8202Wcp9/g+cNIsHTD+JaTKL+xqaIOD9Tjr1yfq7DU44XZ+0pXFssUw83KHAv9X1 GOdg== X-Gm-Message-State: AOAM532m4OvAPzDLrVDMVM0mjY2F3HsjC9g0ThB1VIYmQ6cUiLB6XkD6 g9xYzCUFf8SzUwjPPLi6fL/X6ZaKhfIX7EQx X-Google-Smtp-Source: ABdhPJz5Ze5u3QMyXO4GVXO+/PktVnnyZWmHw+hXT9tyBhAuxCUcZxjSGb4VqL50Zu7aHYGWvn5Cog== X-Received: by 2002:a1c:7f14:: with SMTP id a20mr2943144wmd.112.1625552147462; Mon, 05 Jul 2021 23:15:47 -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 r4sm16333459wre.84.2021.07.05.23.15.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 05 Jul 2021 23:15:46 -0700 (PDT) From: Ronan Dunklau To: pgsql-hackers@lists.postgresql.org Cc: ranier.vf@gmail.com, James Coleman Subject: [PATCH] Use optimized single-datum tuplesort in ExecSort Date: Tue, 06 Jul 2021 08:15:41 +0200 Message-ID: <3177670.itZtoPt7T5@aivenronan> Organization: aiven MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="nextPart2301989.8EcpoIlD2t" 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. --nextPart2301989.8EcpoIlD2t Content-Transfer-Encoding: 7Bit Content-Type: text/plain; charset="us-ascii" Hello, While testing the patch "Add proper planner support for ORDER BY / DISTINCT aggregates" [0] I discovered the performance penalty from adding a sort node essentially came from not using the single-datum tuplesort optimization in ExecSort (contrary to the sorting done in ExecAgg). I originally proposed this patch as a companion in the same thread [1], but following James suggestion I'm making a separate thread just for this as the optimization is worthwhile independently of David's patch: it looks like we can expect a 2x speedup on a "select a single ordered column" case. The patch aimed to be as simple as possible: we only turn this optimization on when the tuple being sorted has only one attribute, it is "byval" (so as not to incur copies which would be hard to track in the execution tree) and unbound (again, not having to deal with copying borrowed datum anywhere). The attached patch is originally by me, with some cleanup by Ranier Vilela. I'm sending Ranier's version here. [0]: https://commitfest.postgresql.org/33/3164/ [1]: https://www.postgresql.org/message-id/4480689.ObhdGn8bVM%40aivenronan -- Ronan Dunklau --nextPart2301989.8EcpoIlD2t Content-Disposition: attachment; filename="v2-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="v2-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..363f1d90f0 100644 --- a/src/backend/executor/nodeSort.c +++ b/src/backend/executor/nodeSort.c @@ -85,16 +85,28 @@ 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); + if (unlikely(tupDesc->natts == 1 && + !node->bounded && + TupleDescAttr(tupDesc, 0)->attbyval)) + { + 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; @@ -103,15 +115,27 @@ ExecSort(PlanState *pstate) * Scan the subplan and feed all the tuples to tuplesort. */ - 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; + tuplesort_puttupleslot(tuplesortstate, slot); + } + else + for (;;) + { + slot = ExecProcNode(outerNode); + + if (TupIsNull(slot)) + break; + slot_getsomeattrs(slot, 1); + tuplesort_putdatum(tuplesortstate, + slot->tts_values[0], + slot->tts_isnull[0]); + } /* * Complete the sort. @@ -150,9 +174,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 +224,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/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; --nextPart2301989.8EcpoIlD2t--