Reduce the number of pallocs when building partition bounds
authorDavid Rowley <drowley@postgresql.org>
Tue, 6 Jul 2021 00:24:43 +0000 (12:24 +1200)
committerDavid Rowley <drowley@postgresql.org>
Tue, 6 Jul 2021 00:24:43 +0000 (12:24 +1200)
In each of the create_*_bound() functions for LIST, RANGE and HASH
partitioning, there were a large number of palloc calls which could be
reduced down to a much smaller number.

In each of these functions, an array was built so that we could qsort it
before making the PartitionBoundInfo. For LIST and HASH partitioning, an
array of pointers was allocated then each element was allocated within
that array.  Since the number of items of each dimension is known
beforehand, we can just allocate a single chunk of memory for this.

Similarly, with all partition strategies, we're able to reduce the number
of allocations to build the ->datums field.  This is an array of Datum
pointers, but there's no need for the Datums that each element points to
to be singly allocated.  One big chunk will do.  For RANGE partitioning,
the PartitionBoundInfo->kind field can get the same treatment.

We can apply the same optimizations to partition_bounds_copy().  Doing
this might have a small effect on cache performance when searching for the
correct partition during partition pruning or DML on a partitioned table.
However, that's likely to be small and this is mostly about reducing
palloc overhead.

Author: Nitin Jadhav, Justin Pryzby, David Rowley
Reviewed-by: Justin Pryzby, Zhihong Yu
Discussion: https://postgr.es/m/flat/CAMm1aWYFTqEio3bURzZh47jveiHRwgQTiSDvBORczNEz2duZ1Q@mail.gmail.com

src/backend/partitioning/partbounds.c

index 7096d3bf4504ab36b1c2e1e92307d54f52c592e0..00c394445a830bcb960ae8215e536f6c1ee3ff27 100644 (file)
@@ -358,10 +358,10 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
                   PartitionKey key, int **mapping)
 {
    PartitionBoundInfo boundinfo;
-   PartitionHashBound **hbounds = NULL;
+   PartitionHashBound *hbounds;
    int         i;
-   int         ndatums = 0;
    int         greatest_modulus;
+   Datum      *boundDatums;
 
    boundinfo = (PartitionBoundInfoData *)
        palloc0(sizeof(PartitionBoundInfoData));
@@ -370,9 +370,8 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
    boundinfo->null_index = -1;
    boundinfo->default_index = -1;
 
-   ndatums = nparts;
-   hbounds = (PartitionHashBound **)
-       palloc(nparts * sizeof(PartitionHashBound *));
+   hbounds = (PartitionHashBound *)
+       palloc(nparts * sizeof(PartitionHashBound));
 
    /* Convert from node to the internal representation */
    for (i = 0; i < nparts; i++)
@@ -382,26 +381,33 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
        if (spec->strategy != PARTITION_STRATEGY_HASH)
            elog(ERROR, "invalid strategy in partition bound spec");
 
-       hbounds[i] = (PartitionHashBound *) palloc(sizeof(PartitionHashBound));
-       hbounds[i]->modulus = spec->modulus;
-       hbounds[i]->remainder = spec->remainder;
-       hbounds[i]->index = i;
+       hbounds[i].modulus = spec->modulus;
+       hbounds[i].remainder = spec->remainder;
+       hbounds[i].index = i;
    }
 
    /* Sort all the bounds in ascending order */
-   qsort(hbounds, nparts, sizeof(PartitionHashBound *),
+   qsort(hbounds, nparts, sizeof(PartitionHashBound),
          qsort_partition_hbound_cmp);
 
    /* After sorting, moduli are now stored in ascending order. */
-   greatest_modulus = hbounds[ndatums - 1]->modulus;
+   greatest_modulus = hbounds[nparts - 1].modulus;
 
-   boundinfo->ndatums = ndatums;
-   boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+   boundinfo->ndatums = nparts;
+   boundinfo->datums = (Datum **) palloc0(nparts * sizeof(Datum *));
+   boundinfo->kind = NULL;
    boundinfo->nindexes = greatest_modulus;
    boundinfo->indexes = (int *) palloc(greatest_modulus * sizeof(int));
    for (i = 0; i < greatest_modulus; i++)
        boundinfo->indexes[i] = -1;
 
+   /*
+    * In the loop below, to save from allocating a series of small datum
+    * arrays, here we just allocate a single array and below we'll just
+    * assign a portion of this array per partition.
+    */
+   boundDatums = (Datum *) palloc(nparts * 2 * sizeof(Datum));
+
    /*
     * For hash partitioning, there are as many datums (modulus and remainder
     * pairs) as there are partitions.  Indexes are simply values ranging from
@@ -409,10 +415,10 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
     */
    for (i = 0; i < nparts; i++)
    {
-       int         modulus = hbounds[i]->modulus;
-       int         remainder = hbounds[i]->remainder;
+       int         modulus = hbounds[i].modulus;
+       int         remainder = hbounds[i].remainder;
 
-       boundinfo->datums[i] = (Datum *) palloc(2 * sizeof(Datum));
+       boundinfo->datums[i] = &boundDatums[i * 2];
        boundinfo->datums[i][0] = Int32GetDatum(modulus);
        boundinfo->datums[i][1] = Int32GetDatum(remainder);
 
@@ -424,14 +430,39 @@ create_hash_bounds(PartitionBoundSpec **boundspecs, int nparts,
            remainder += modulus;
        }
 
-       (*mapping)[hbounds[i]->index] = i;
-       pfree(hbounds[i]);
+       (*mapping)[hbounds[i].index] = i;
    }
    pfree(hbounds);
 
    return boundinfo;
 }
 
+/*
+ * get_non_null_list_datum_count
+ *         Counts the number of non-null Datums in each partition.
+ */
+static int
+get_non_null_list_datum_count(PartitionBoundSpec **boundspecs, int nparts)
+{
+   int         i;
+   int         count = 0;
+
+   for (i = 0; i < nparts; i++)
+   {
+       ListCell   *lc;
+
+       foreach(lc, boundspecs[i]->listdatums)
+       {
+           Const      *val = castNode(Const, lfirst(lc));
+
+           if (!val->constisnull)
+               count++;
+       }
+   }
+
+   return count;
+}
+
 /*
  * create_list_bounds
  *     Create a PartitionBoundInfo for a list partitioned table
@@ -441,14 +472,14 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
                   PartitionKey key, int **mapping)
 {
    PartitionBoundInfo boundinfo;
-   PartitionListValue **all_values = NULL;
-   ListCell   *cell;
-   int         i = 0;
-   int         ndatums = 0;
+   PartitionListValue *all_values;
+   int         i;
+   int         j;
+   int         ndatums;
    int         next_index = 0;
    int         default_index = -1;
    int         null_index = -1;
-   List       *non_null_values = NIL;
+   Datum      *boundDatums;
 
    boundinfo = (PartitionBoundInfoData *)
        palloc0(sizeof(PartitionBoundInfoData));
@@ -457,8 +488,12 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
    boundinfo->null_index = -1;
    boundinfo->default_index = -1;
 
+   ndatums = get_non_null_list_datum_count(boundspecs, nparts);
+   all_values = (PartitionListValue *)
+       palloc(ndatums * sizeof(PartitionListValue));
+
    /* Create a unified list of non-null values across all partitions. */
-   for (i = 0; i < nparts; i++)
+   for (j = 0, i = 0; i < nparts; i++)
    {
        PartitionBoundSpec *spec = boundspecs[i];
        ListCell   *c;
@@ -480,14 +515,12 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
        foreach(c, spec->listdatums)
        {
            Const      *val = castNode(Const, lfirst(c));
-           PartitionListValue *list_value = NULL;
 
            if (!val->constisnull)
            {
-               list_value = (PartitionListValue *)
-                   palloc0(sizeof(PartitionListValue));
-               list_value->index = i;
-               list_value->value = val->constvalue;
+               all_values[j].index = i;
+               all_values[j].value = val->constvalue;
+               j++;
            }
            else
            {
@@ -499,40 +532,28 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
                    elog(ERROR, "found null more than once");
                null_index = i;
            }
-
-           if (list_value)
-               non_null_values = lappend(non_null_values, list_value);
        }
    }
 
-   ndatums = list_length(non_null_values);
-
-   /*
-    * Collect all list values in one array. Alongside the value, we also save
-    * the index of partition the value comes from.
-    */
-   all_values = (PartitionListValue **)
-       palloc(ndatums * sizeof(PartitionListValue *));
-   i = 0;
-   foreach(cell, non_null_values)
-   {
-       PartitionListValue *src = lfirst(cell);
-
-       all_values[i] = (PartitionListValue *)
-           palloc(sizeof(PartitionListValue));
-       all_values[i]->value = src->value;
-       all_values[i]->index = src->index;
-       i++;
-   }
+   /* ensure we found a Datum for every slot in the all_values array */
+   Assert(j == ndatums);
 
-   qsort_arg(all_values, ndatums, sizeof(PartitionListValue *),
+   qsort_arg(all_values, ndatums, sizeof(PartitionListValue),
              qsort_partition_list_value_cmp, (void *) key);
 
    boundinfo->ndatums = ndatums;
    boundinfo->datums = (Datum **) palloc0(ndatums * sizeof(Datum *));
+   boundinfo->kind = NULL;
    boundinfo->nindexes = ndatums;
    boundinfo->indexes = (int *) palloc(ndatums * sizeof(int));
 
+   /*
+    * In the loop below, to save from allocating a series of small datum
+    * arrays, here we just allocate a single array and below we'll just
+    * assign a portion of this array per datum.
+    */
+   boundDatums = (Datum *) palloc(ndatums * sizeof(Datum));
+
    /*
     * Copy values.  Canonical indexes are values ranging from 0 to (nparts -
     * 1) assigned to each partition such that all datums of a given partition
@@ -541,10 +562,10 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
     */
    for (i = 0; i < ndatums; i++)
    {
-       int         orig_index = all_values[i]->index;
+       int         orig_index = all_values[i].index;
 
-       boundinfo->datums[i] = (Datum *) palloc(sizeof(Datum));
-       boundinfo->datums[i][0] = datumCopy(all_values[i]->value,
+       boundinfo->datums[i] = &boundDatums[i];
+       boundinfo->datums[i][0] = datumCopy(all_values[i].value,
                                            key->parttypbyval[0],
                                            key->parttyplen[0]);
 
@@ -555,6 +576,8 @@ create_list_bounds(PartitionBoundSpec **boundspecs, int nparts,
        boundinfo->indexes[i] = (*mapping)[orig_index];
    }
 
+   pfree(all_values);
+
    /*
     * Set the canonical value for null_index, if any.
     *
@@ -603,10 +626,13 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
    PartitionRangeBound **all_bounds,
               *prev;
    int         i,
-               k;
+               k,
+               partnatts;
    int         ndatums = 0;
    int         default_index = -1;
    int         next_index = 0;
+   Datum      *boundDatums;
+   PartitionRangeDatumKind *boundKinds;
 
    boundinfo = (PartitionBoundInfoData *)
        palloc0(sizeof(PartitionBoundInfoData));
@@ -707,6 +733,8 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
        prev = cur;
    }
 
+   pfree(all_bounds);
+
    /* Update ndatums to hold the count of distinct datums. */
    ndatums = k;
 
@@ -731,16 +759,24 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
    boundinfo->nindexes = ndatums + 1;
    boundinfo->indexes = (int *) palloc((ndatums + 1) * sizeof(int));
 
+   /*
+    * In the loop below, to save from allocating a series of small arrays,
+    * here we just allocate a single array for Datums and another for
+    * PartitionRangeDatumKinds, below we'll just assign a portion of these
+    * arrays in each loop.
+    */
+   partnatts = key->partnatts;
+   boundDatums = (Datum *) palloc(ndatums * partnatts * sizeof(Datum));
+   boundKinds = (PartitionRangeDatumKind *) palloc(ndatums * partnatts *
+                                                   sizeof(PartitionRangeDatumKind));
+
    for (i = 0; i < ndatums; i++)
    {
        int         j;
 
-       boundinfo->datums[i] = (Datum *) palloc(key->partnatts *
-                                               sizeof(Datum));
-       boundinfo->kind[i] = (PartitionRangeDatumKind *)
-           palloc(key->partnatts *
-                  sizeof(PartitionRangeDatumKind));
-       for (j = 0; j < key->partnatts; j++)
+       boundinfo->datums[i] = &boundDatums[i * partnatts];
+       boundinfo->kind[i] = &boundKinds[i * partnatts];
+       for (j = 0; j < partnatts; j++)
        {
            if (rbounds[i]->kind[j] == PARTITION_RANGE_DATUM_VALUE)
                boundinfo->datums[i][j] =
@@ -772,6 +808,8 @@ create_range_bounds(PartitionBoundSpec **boundspecs, int nparts,
        }
    }
 
+   pfree(rbounds);
+
    /* Set the canonical value for default_index, if any. */
    if (default_index != -1)
    {
@@ -914,6 +952,7 @@ partition_bounds_copy(PartitionBoundInfo src,
    int         partnatts;
    bool        hash_part;
    int         natts;
+   Datum      *boundDatums;
 
    dest = (PartitionBoundInfo) palloc(sizeof(PartitionBoundInfoData));
 
@@ -929,15 +968,27 @@ partition_bounds_copy(PartitionBoundInfo src,
 
    if (src->kind != NULL)
    {
+       PartitionRangeDatumKind *boundKinds;
+
+       /* only RANGE partition should have a non-NULL kind */
+       Assert(key->strategy == PARTITION_STRATEGY_RANGE);
+
        dest->kind = (PartitionRangeDatumKind **) palloc(ndatums *
                                                         sizeof(PartitionRangeDatumKind *));
+
+       /*
+        * In the loop below, to save from allocating a series of small arrays
+        * for storing the PartitionRangeDatumKind, we allocate a single chunk
+        * here and use a smaller portion of it for each datum.
+        */
+       boundKinds = (PartitionRangeDatumKind *) palloc(ndatums * partnatts *
+                                                       sizeof(PartitionRangeDatumKind));
+
        for (i = 0; i < ndatums; i++)
        {
-           dest->kind[i] = (PartitionRangeDatumKind *) palloc(partnatts *
-                                                              sizeof(PartitionRangeDatumKind));
-
+           dest->kind[i] = &boundKinds[i * partnatts];
            memcpy(dest->kind[i], src->kind[i],
-                  sizeof(PartitionRangeDatumKind) * key->partnatts);
+                  sizeof(PartitionRangeDatumKind) * partnatts);
        }
    }
    else
@@ -949,12 +1000,13 @@ partition_bounds_copy(PartitionBoundInfo src,
     */
    hash_part = (key->strategy == PARTITION_STRATEGY_HASH);
    natts = hash_part ? 2 : partnatts;
+   boundDatums = palloc(ndatums * natts * sizeof(Datum));
 
    for (i = 0; i < ndatums; i++)
    {
        int         j;
 
-       dest->datums[i] = (Datum *) palloc(sizeof(Datum) * natts);
+       dest->datums[i] = &boundDatums[i * natts];
 
        for (j = 0; j < natts; j++)
        {
@@ -3682,8 +3734,8 @@ partition_hash_bsearch(PartitionBoundInfo boundinfo,
 static int32
 qsort_partition_hbound_cmp(const void *a, const void *b)
 {
-   PartitionHashBound *h1 = (*(PartitionHashBound *const *) a);
-   PartitionHashBound *h2 = (*(PartitionHashBound *const *) b);
+   PartitionHashBound *const h1 = (PartitionHashBound *const) a;
+   PartitionHashBound *const h2 = (PartitionHashBound *const) b;
 
    return partition_hbound_cmp(h1->modulus, h1->remainder,
                                h2->modulus, h2->remainder);
@@ -3697,8 +3749,8 @@ qsort_partition_hbound_cmp(const void *a, const void *b)
 static int32
 qsort_partition_list_value_cmp(const void *a, const void *b, void *arg)
 {
-   Datum       val1 = (*(PartitionListValue *const *) a)->value,
-               val2 = (*(PartitionListValue *const *) b)->value;
+   Datum       val1 = ((PartitionListValue *const) a)->value,
+               val2 = ((PartitionListValue *const) b)->value;
    PartitionKey key = (PartitionKey) arg;
 
    return DatumGetInt32(FunctionCall2Coll(&key->partsupfunc[0],