]> granicus.if.org Git - postgresql/commitdiff
Speed up in-memory tuplesorting.
authorRobert Haas <rhaas@postgresql.org>
Wed, 15 Feb 2012 17:13:32 +0000 (12:13 -0500)
committerRobert Haas <rhaas@postgresql.org>
Wed, 15 Feb 2012 17:13:32 +0000 (12:13 -0500)
Per recent work by Peter Geoghegan, it's significantly faster to
tuplesort on a single sortkey if ApplySortComparator is inlined into
quicksort rather reached via a function pointer.  It's also faster
in general to have a version of quicksort which is specialized for
sorting SortTuple objects rather than objects of arbitrary size and
type.  This requires a couple of additional copies of the quicksort
logic, which in this patch are generate using a Perl script.  There
might be some benefit in adding further specializations here too,
but thus far it's not clear that those gains are worth their weight
in code footprint.

src/backend/Makefile
src/backend/utils/sort/.gitignore [new file with mode: 0644]
src/backend/utils/sort/Makefile
src/backend/utils/sort/gen_qsort_tuple.pl [new file with mode: 0644]
src/backend/utils/sort/tuplesort.c
src/port/qsort.c
src/port/qsort_arg.c
src/tools/msvc/Solution.pm

index 0c763dd37584eb9208a528ff4de5326209a09533..01bb6e1171dbd6655636005ef80563052648f000 100644 (file)
@@ -202,6 +202,7 @@ distprep:
        $(MAKE) -C replication  repl_gram.c repl_scanner.c
        $(MAKE) -C utils        fmgrtab.c fmgroids.h errcodes.h
        $(MAKE) -C utils/misc   guc-file.c
+       $(MAKE) -C utils/sort   qsort_tuple.c
 
 
 ##########################################################################
@@ -315,7 +316,8 @@ maintainer-clean: distclean
              utils/fmgroids.h \
              utils/fmgrtab.c \
              utils/errcodes.h \
-             utils/misc/guc-file.c
+             utils/misc/guc-file.c \
+             utils/misc/qsort_tuple.c
 
 
 ##########################################################################
diff --git a/src/backend/utils/sort/.gitignore b/src/backend/utils/sort/.gitignore
new file mode 100644 (file)
index 0000000..f295863
--- /dev/null
@@ -0,0 +1 @@
+/qsort_tuple.c
index 2ef4965ee6db442f3d2e94903d76a4cdb69db521..f46ce4161052976493c4b130e7b16937154c8bea 100644 (file)
@@ -14,4 +14,12 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = logtape.o sortsupport.o tuplesort.o tuplestore.o
 
+tuplesort.o: qsort_tuple.c
+
+qsort_tuple.c: gen_qsort_tuple.pl
+       $(PERL) $(srcdir)/gen_qsort_tuple.pl $< > $@
+
 include $(top_srcdir)/src/backend/common.mk
+
+maintainer-clean:
+       rm -f qsort_tuple.c
diff --git a/src/backend/utils/sort/gen_qsort_tuple.pl b/src/backend/utils/sort/gen_qsort_tuple.pl
new file mode 100644 (file)
index 0000000..40d5548
--- /dev/null
@@ -0,0 +1,232 @@
+#!/usr/bin/perl -w
+
+#
+# gen_qsort_tuple.pl
+#
+# This script generates specialized versions of the quicksort algorithm for
+# tuple sorting.  The quicksort code is derived from the NetBSD code.  The
+# code generated by this script runs significantly faster than vanilla qsort
+# when used to sort tuples.  This speedup comes from a number of places.
+# The major effects are (1) inlining simple tuple comparators is much faster
+# than jumping through a function pointer and (2) swap and vecswap operations
+# specialized to the particular data type of interest (in this case, SortTuple)
+# are faster than the generic routines.
+#
+#      Modifications from vanilla NetBSD source:
+#        Add do ... while() macro fix
+#        Remove __inline, _DIAGASSERTs, __P
+#        Remove ill-considered "swap_cnt" switch to insertion sort,
+#        in favor of a simple check for presorted input.
+#     Instead of sorting arbitrary objects, we're always sorting SortTuples
+#     Add CHECK_FOR_INTERRUPTS()
+#
+# CAUTION: if you change this file, see also qsort.c and qsort_arg.c
+#
+
+use strict;
+
+my $SUFFIX;
+my $EXTRAARGS;
+my $EXTRAPARAMS;
+my $CMPPARAMS;
+
+emit_qsort_boilerplate();
+
+$SUFFIX = 'tuple';
+$EXTRAARGS = ', SortTupleComparator cmp_tuple, Tuplesortstate *state';
+$EXTRAPARAMS = ', cmp_tuple, state';
+$CMPPARAMS = ', state';
+emit_qsort_implementation();
+
+$SUFFIX = 'ssup';
+$EXTRAARGS = ', SortSupport ssup';
+$EXTRAPARAMS = ', ssup';
+$CMPPARAMS = ', ssup';
+print <<'EOM';
+#define cmp_ssup(a, b, ssup) \
+       ApplySortComparator((a)->datum1, (a)->isnull1, \
+                                               (b)->datum1, (b)->isnull1, ssup)
+EOM
+emit_qsort_implementation();
+
+sub emit_qsort_boilerplate
+{
+       print <<'EOM';
+/*
+ * autogenerated by src/backend/utils/sort/gen_qsort_tuple.pl, do not edit
+ * This file is included by tuplesort.c, rather than compiled separately.
+ */
+
+/*     $NetBSD: qsort.c,v 1.13 2003/08/07 16:43:42 agc Exp $   */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in the
+ *       documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *       may be used to endorse or promote products derived from this software
+ *       without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.     IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Qsort routine based on J. L. Bentley and M. D. McIlroy,
+ * "Engineering a sort function",
+ * Software--Practice and Experience 23 (1993) 1249-1265.
+ * We have modified their original by adding a check for already-sorted input,
+ * which seems to be a win per discussions on pgsql-hackers around 2006-03-21.
+ */
+
+static void
+swapfunc(SortTuple *a, SortTuple *b, size_t n)
+{
+       do
+       {
+               SortTuple       t = *a;
+               *a++ = *b;
+               *b++ = t;
+       } while (--n > 0);
+}
+
+#define swap(a, b)                                             \
+       do {                                                            \
+               SortTuple t = *(a);                             \
+               *(a) = *(b);                                    \
+               *(b) = t;                                               \
+       } while (0);
+
+#define vecswap(a, b, n) if ((n) > 0) swapfunc((a), (b), (size_t)(n))
+EOM
+}
+
+sub emit_qsort_implementation
+{
+       print <<EOM;
+static SortTuple *
+med3_$SUFFIX(SortTuple *a, SortTuple *b, SortTuple *c$EXTRAARGS)
+{
+       return cmp_$SUFFIX(a, b$CMPPARAMS) < 0 ?
+               (cmp_$SUFFIX(b, c$CMPPARAMS) < 0 ? b :
+                       (cmp_$SUFFIX(a, c$CMPPARAMS) < 0 ? c : a))
+               : (cmp_$SUFFIX(b, c$CMPPARAMS) > 0 ? b :
+                       (cmp_$SUFFIX(a, c$CMPPARAMS) < 0 ? a : c));
+}
+
+static void
+qsort_$SUFFIX(SortTuple *a, size_t n$EXTRAARGS)
+{
+       SortTuple  *pa,
+                          *pb,
+                          *pc,
+                          *pd,
+                          *pl,
+                          *pm,
+                          *pn;
+       int                     d,
+                               r,
+                               presorted;
+
+loop:
+       CHECK_FOR_INTERRUPTS();
+       if (n < 7)
+       {
+               for (pm = a + 1; pm < a + n; pm++)
+                       for (pl = pm; pl > a && cmp_$SUFFIX(pl - 1, pl$CMPPARAMS) > 0; pl--)
+                               swap(pl, pl - 1);
+               return;
+       }
+       presorted = 1;
+       for (pm = a + 1; pm < a + n; pm++)
+       {
+               CHECK_FOR_INTERRUPTS();
+               if (cmp_$SUFFIX(pm - 1, pm$CMPPARAMS) > 0)
+               {
+                       presorted = 0;
+                       break;
+               }
+       }
+       if (presorted)
+               return;
+       pm = a + (n / 2);
+       if (n > 7)
+       {
+               pl = a;
+               pn = a + (n - 1);
+               if (n > 40)
+               {
+                       d = (n / 8);
+                       pl = med3_$SUFFIX(pl, pl + d, pl + 2 * d$EXTRAPARAMS);
+                       pm = med3_$SUFFIX(pm - d, pm, pm + d$EXTRAPARAMS);
+                       pn = med3_$SUFFIX(pn - 2 * d, pn - d, pn$EXTRAPARAMS);
+               }
+               pm = med3_$SUFFIX(pl, pm, pn$EXTRAPARAMS);
+       }
+       swap(a, pm);
+       pa = pb = a + 1;
+       pc = pd = a + (n - 1);
+       for (;;)
+       {
+               while (pb <= pc && (r = cmp_$SUFFIX(pb, a$CMPPARAMS)) <= 0)
+               {
+                       CHECK_FOR_INTERRUPTS();
+                       if (r == 0)
+                       {
+                               swap(pa, pb);
+                               pa++;
+                       }
+                       pb++;
+               }
+               while (pb <= pc && (r = cmp_$SUFFIX(pc, a$CMPPARAMS)) >= 0)
+               {
+                       CHECK_FOR_INTERRUPTS();
+                       if (r == 0)
+                       {
+                               swap(pc, pd);
+                               pd--;
+                       }
+                       pc--;
+               }
+               if (pb > pc)
+                       break;
+               swap(pb, pc);
+               pb++;
+               pc--;
+       }
+       pn = a + n;
+       r = Min(pa - a, pb - pa);
+       vecswap(a, pb - r, r);
+       r = Min(pd - pc, pn - pd - 1);
+       vecswap(pb, pn - r, r);
+       if ((r = pb - pa) > 1)
+               qsort_$SUFFIX(a, r$EXTRAPARAMS);
+       if ((r = pd - pc) > 1)
+       {
+               /* Iterate rather than recurse to save stack space */
+               a = pn - r;
+               n = r;
+               goto loop;
+       }
+/*             qsort_$SUFFIX(pn - r, r$EXTRAPARAMS);*/
+}
+
+EOM
+}
index 1452e8c7cfcb94919ae914c3bc4912f3de5a0028..10b19c0b21f526effe6d2cad73b25f90046f9cc2 100644 (file)
@@ -195,6 +195,9 @@ typedef enum
 #define TAPE_BUFFER_OVERHEAD           (BLCKSZ * 3)
 #define MERGE_BUFFER_SIZE                      (BLCKSZ * 32)
 
+typedef int    (*SortTupleComparator) (const SortTuple *a, const SortTuple *b,
+       Tuplesortstate *state);
+
 /*
  * Private state of a Tuplesort operation.
  */
@@ -223,8 +226,7 @@ struct Tuplesortstate
         * <0, 0, >0 according as a<b, a=b, a>b.  The API must match
         * qsort_arg_comparator.
         */
-       int                     (*comparetup) (const SortTuple *a, const SortTuple *b,
-                                                                                  Tuplesortstate *state);
+       SortTupleComparator     comparetup;
 
        /*
         * Function to copy a supplied input tuple into palloc'd space and set up
@@ -363,12 +365,14 @@ struct Tuplesortstate
        /* These are specific to the index_hash subcase: */
        uint32          hash_mask;              /* mask for sortable part of hash code */
 
+       /* This is initialized when, and only when, there's just one key. */
+       SortSupport     onlyKey;
+
        /*
         * These variables are specific to the Datum case; they are set by
         * tuplesort_begin_datum and used only by the DatumTuple routines.
         */
        Oid                     datumType;
-       SortSupport     datumKey;
        /* we need typelen and byval in order to know how to copy the Datums. */
        int                     datumTypeLen;
        bool            datumTypeByVal;
@@ -492,6 +496,11 @@ static void readtup_datum(Tuplesortstate *state, SortTuple *stup,
 static void reversedirection_datum(Tuplesortstate *state);
 static void free_sort_tuple(Tuplesortstate *state, SortTuple *stup);
 
+/*
+ * Special version of qsort, just for SortTuple objects.
+ */
+#include "qsort_tuple.c"
+
 
 /*
  *             tuplesort_begin_xxx
@@ -631,6 +640,9 @@ tuplesort_begin_heap(TupleDesc tupDesc,
                PrepareSortSupportFromOrderingOp(sortOperators[i], sortKey);
        }
 
+       if (nkeys == 1)
+               state->onlyKey = state->sortKeys;
+
        MemoryContextSwitchTo(oldcontext);
 
        return state;
@@ -809,13 +821,13 @@ tuplesort_begin_datum(Oid datumType, Oid sortOperator, Oid sortCollation,
        state->datumType = datumType;
 
        /* Prepare SortSupport data */
-       state->datumKey = (SortSupport) palloc0(sizeof(SortSupportData));
+       state->onlyKey = (SortSupport) palloc0(sizeof(SortSupportData));
 
-       state->datumKey->ssup_cxt = CurrentMemoryContext;
-       state->datumKey->ssup_collation = sortCollation;
-       state->datumKey->ssup_nulls_first = nullsFirstFlag;
+       state->onlyKey->ssup_cxt = CurrentMemoryContext;
+       state->onlyKey->ssup_collation = sortCollation;
+       state->onlyKey->ssup_nulls_first = nullsFirstFlag;
 
-       PrepareSortSupportFromOrderingOp(sortOperator, state->datumKey);
+       PrepareSortSupportFromOrderingOp(sortOperator, state->onlyKey);
 
        /* lookup necessary attributes of the datum type */
        get_typlenbyval(datumType, &typlen, &typbyval);
@@ -1222,11 +1234,16 @@ tuplesort_performsort(Tuplesortstate *state)
                         * amount of memory.  Just qsort 'em and we're done.
                         */
                        if (state->memtupcount > 1)
-                               qsort_arg((void *) state->memtuples,
-                                                 state->memtupcount,
-                                                 sizeof(SortTuple),
-                                                 (qsort_arg_comparator) state->comparetup,
-                                                 (void *) state);
+                       {
+                               if (state->onlyKey != NULL)
+                                       qsort_ssup(state->memtuples, state->memtupcount,
+                                                          state->onlyKey);
+                               else
+                                       qsort_tuple(state->memtuples,
+                                                               state->memtupcount,
+                                                               state->comparetup,
+                                                               state);
+                       }
                        state->current = 0;
                        state->eof_reached = false;
                        state->markpos_offset = 0;
@@ -2660,9 +2677,6 @@ comparetup_heap(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
        int                     nkey;
        int32           compare;
 
-       /* Allow interrupting long sorts */
-       CHECK_FOR_INTERRUPTS();
-
        /* Compare the leading sort key */
        compare = ApplySortComparator(a->datum1, a->isnull1,
                                                                  b->datum1, b->isnull1,
@@ -2804,9 +2818,6 @@ comparetup_cluster(const SortTuple *a, const SortTuple *b,
        int                     nkey;
        int32           compare;
 
-       /* Allow interrupting long sorts */
-       CHECK_FOR_INTERRUPTS();
-
        /* Compare the leading sort key, if it's simple */
        if (state->indexInfo->ii_KeyAttrNumbers[0] != 0)
        {
@@ -2995,9 +3006,6 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b,
        int                     nkey;
        int32           compare;
 
-       /* Allow interrupting long sorts */
-       CHECK_FOR_INTERRUPTS();
-
        /* Compare the leading sort key */
        compare = inlineApplySortFunction(&scanKey->sk_func, scanKey->sk_flags,
                                                                          scanKey->sk_collation,
@@ -3102,9 +3110,6 @@ comparetup_index_hash(const SortTuple *a, const SortTuple *b,
        IndexTuple      tuple1;
        IndexTuple      tuple2;
 
-       /* Allow interrupting long sorts */
-       CHECK_FOR_INTERRUPTS();
-
        /*
         * Fetch hash keys and mask off bits we don't want to sort by. We know
         * that the first column of the index tuple is the hash key.
@@ -3231,12 +3236,9 @@ reversedirection_index_hash(Tuplesortstate *state)
 static int
 comparetup_datum(const SortTuple *a, const SortTuple *b, Tuplesortstate *state)
 {
-       /* Allow interrupting long sorts */
-       CHECK_FOR_INTERRUPTS();
-
-       return ApplySortComparator(a->datum1, a->isnull1,
-                                                          b->datum1, b->isnull1,
-                                                          state->datumKey);
+       /* Not currently needed */
+       elog(ERROR, "comparetup_datum() should not be called");
+       return 0;
 }
 
 static void
@@ -3328,8 +3330,8 @@ readtup_datum(Tuplesortstate *state, SortTuple *stup,
 static void
 reversedirection_datum(Tuplesortstate *state)
 {
-       state->datumKey->ssup_reverse = !state->datumKey->ssup_reverse;
-       state->datumKey->ssup_nulls_first = !state->datumKey->ssup_nulls_first;
+       state->onlyKey->ssup_reverse = !state->onlyKey->ssup_reverse;
+       state->onlyKey->ssup_nulls_first = !state->onlyKey->ssup_nulls_first;
 }
 
 /*
index 8e2c6d92c2dc99ec2b64d23367ca62505e89405e..49d8fa7ab6cbdc82b025bb54cdc7414a571d8972 100644 (file)
@@ -7,7 +7,7 @@
  *       Remove ill-considered "swap_cnt" switch to insertion sort,
  *       in favor of a simple check for presorted input.
  *
- *     CAUTION: if you change this file, see also qsort_arg.c
+ *     CAUTION: if you change this file, see also qsort_arg.c, gen_qsort_tuple.pl
  *
  *     src/port/qsort.c
  */
index 28d1894992b93a767d973523ff09a163f7a29258..3091eb09eada9860ec19efa3eef2c28c4cdb34a8 100644 (file)
@@ -7,7 +7,7 @@
  *       Remove ill-considered "swap_cnt" switch to insertion sort,
  *       in favor of a simple check for presorted input.
  *
- *     CAUTION: if you change this file, see also qsort.c
+ *     CAUTION: if you change this file, see also qsort.c, gen_qsort_tuple.pl
  *
  *     src/port/qsort_arg.c
  */
index 1725fbb5330c680df88edc6003f9f664e1ee2907..e1d85c85ad030b8481d9729bf10a0c60e7bd9ac0 100644 (file)
@@ -287,6 +287,14 @@ s{PG_VERSION_STR "[^"]+"}{__STRINGIFY(x) #x\n#define __STRINGIFY2(z) __STRINGIFY
         );
     }
 
+    if (IsNewer('src\backend\utils\sort\qsort_tuple.c','src\backend\utils\sort\gen_qsort_tuple.pl'))
+    {
+        print "Generating qsort_tuple.c...\n";
+        system(
+'perl src\backend\utils\sort\gen_qsort_tuple.pl > src\backend\utils\sort\qsort_tuple.c'
+        );
+    }
+
     if (IsNewer('src\interfaces\libpq\libpq.rc','src\interfaces\libpq\libpq.rc.in'))
     {
         print "Generating libpq.rc...\n";