]> granicus.if.org Git - postgresql/commitdiff
In array_agg(), don't create a new context for every group.
authorJeff Davis <jdavis@postgresql.org>
Sun, 22 Feb 2015 01:24:48 +0000 (17:24 -0800)
committerJeff Davis <jdavis@postgresql.org>
Sun, 22 Feb 2015 01:24:48 +0000 (17:24 -0800)
Previously, each new array created a new memory context that started
out at 8kB. This is incredibly wasteful when there are lots of small
groups of just a few elements each.

Change initArrayResult() and friends to accept a "subcontext" argument
to indicate whether the caller wants the ArrayBuildState allocated in
a new subcontext or not. If not, it can no longer be released
separately from the rest of the memory context.

Fixes bug report by Frank van Vugt on 2013-10-19.

Tomas Vondra. Reviewed by Ali Akbar, Tom Lane, and me.

src/backend/executor/nodeSubplan.c
src/backend/utils/adt/array_userfuncs.c
src/backend/utils/adt/arrayfuncs.c
src/backend/utils/adt/xml.c
src/include/utils/array.h
src/pl/plperl/plperl.c

index f3ce1d714bd1192d12545a67d1eb22716d70d164..9eb4d63e1ac0f712fe0c8a0356fddd3fe88c7085 100644 (file)
@@ -262,7 +262,7 @@ ExecScanSubPlan(SubPlanState *node,
        /* Initialize ArrayBuildStateAny in caller's context, if needed */
        if (subLinkType == ARRAY_SUBLINK)
                astate = initArrayResultAny(subplan->firstColType,
-                                                                       CurrentMemoryContext);
+                                                                       CurrentMemoryContext, true);
 
        /*
         * We are probably in a short-lived expression-evaluation context. Switch
@@ -964,7 +964,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
        /* Initialize ArrayBuildStateAny in caller's context, if needed */
        if (subLinkType == ARRAY_SUBLINK)
                astate = initArrayResultAny(subplan->firstColType,
-                                                                       CurrentMemoryContext);
+                                                                       CurrentMemoryContext, true);
 
        /*
         * Must switch to per-query memory context.
index 7f7c2569861564dae8cf85318940735cd0ad4460..5781b952f97b74c2df9918015f9673dc08dfa10c 100644 (file)
@@ -520,8 +520,13 @@ array_agg_transfn(PG_FUNCTION_ARGS)
                elog(ERROR, "array_agg_transfn called in non-aggregate context");
        }
 
-       state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+       if (PG_ARGISNULL(0))
+               state = initArrayResult(arg1_typeid, aggcontext, false);
+       else
+               state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
        elem = PG_ARGISNULL(1) ? (Datum) 0 : PG_GETARG_DATUM(1);
+
        state = accumArrayResult(state,
                                                         elem,
                                                         PG_ARGISNULL(1),
@@ -595,7 +600,12 @@ array_agg_array_transfn(PG_FUNCTION_ARGS)
                elog(ERROR, "array_agg_array_transfn called in non-aggregate context");
        }
 
-       state = PG_ARGISNULL(0) ? NULL : (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+
+       if (PG_ARGISNULL(0))
+               state = initArrayResultArr(arg1_typeid, InvalidOid, aggcontext, false);
+       else
+               state = (ArrayBuildStateArr *) PG_GETARG_POINTER(0);
+
        state = accumArrayResultArr(state,
                                                                PG_GETARG_DATUM(1),
                                                                PG_ARGISNULL(1),
index 79aefaf6a4a75bbb593ea22330bc919b38836283..54979fa6368a3e72fbd191a62fd432b1aaf313ab 100644 (file)
@@ -4650,6 +4650,7 @@ array_insert_slice(ArrayType *destArray,
  *
  *     element_type is the array element type (must be a valid array element type)
  *     rcontext is where to keep working state
+ *     subcontext is a flag determining whether to use a separate memory context
  *
  * Note: there are two common schemes for using accumArrayResult().
  * In the older scheme, you start with a NULL ArrayBuildState pointer, and
@@ -4659,24 +4660,39 @@ array_insert_slice(ArrayType *destArray,
  * once per element.  In this scheme you always end with a non-NULL pointer
  * that you can pass to makeArrayResult; you get an empty array if there
  * were no elements.  This is preferred if an empty array is what you want.
+ *
+ * It's possible to choose whether to create a separate memory context for the
+ * array build state, or whether to allocate it directly within rcontext.
+ *
+ * When there are many concurrent small states (e.g. array_agg() using hash
+ * aggregation of many small groups), using a separate memory context for each
+ * one may result in severe memory bloat. In such cases, use the same memory
+ * context to initialize all such array build states, and pass
+ * subcontext=false.
+ *
+ * In cases when the array build states have different lifetimes, using a
+ * single memory context is impractical. Instead, pass subcontext=true so that
+ * the array build states can be freed individually.
  */
 ArrayBuildState *
-initArrayResult(Oid element_type, MemoryContext rcontext)
+initArrayResult(Oid element_type, MemoryContext rcontext, bool subcontext)
 {
        ArrayBuildState *astate;
-       MemoryContext arr_context;
+       MemoryContext arr_context = rcontext;
 
        /* Make a temporary context to hold all the junk */
-       arr_context = AllocSetContextCreate(rcontext,
-                                                                               "accumArrayResult",
-                                                                               ALLOCSET_DEFAULT_MINSIZE,
-                                                                               ALLOCSET_DEFAULT_INITSIZE,
-                                                                               ALLOCSET_DEFAULT_MAXSIZE);
+       if (subcontext)
+               arr_context = AllocSetContextCreate(rcontext,
+                                                                                       "accumArrayResult",
+                                                                                       ALLOCSET_DEFAULT_MINSIZE,
+                                                                                       ALLOCSET_DEFAULT_INITSIZE,
+                                                                                       ALLOCSET_DEFAULT_MAXSIZE);
 
        astate = (ArrayBuildState *)
                MemoryContextAlloc(arr_context, sizeof(ArrayBuildState));
        astate->mcontext = arr_context;
-       astate->alen = 64;                      /* arbitrary starting array size */
+       astate->private_cxt = subcontext;
+       astate->alen = (subcontext ? 64 : 8);   /* arbitrary starting array size */
        astate->dvalues = (Datum *)
                MemoryContextAlloc(arr_context, astate->alen * sizeof(Datum));
        astate->dnulls = (bool *)
@@ -4710,7 +4726,7 @@ accumArrayResult(ArrayBuildState *astate,
        if (astate == NULL)
        {
                /* First time through --- initialize */
-               astate = initArrayResult(element_type, rcontext);
+               astate = initArrayResult(element_type, rcontext, true);
        }
        else
        {
@@ -4757,6 +4773,9 @@ accumArrayResult(ArrayBuildState *astate,
 /*
  * makeArrayResult - produce 1-D final result of accumArrayResult
  *
+ * Note: only releases astate if it was initialized within a separate memory
+ * context (i.e. using subcontext=true when calling initArrayResult).
+ *
  *     astate is working state (must not be NULL)
  *     rcontext is where to construct result
  */
@@ -4773,7 +4792,8 @@ makeArrayResult(ArrayBuildState *astate,
        dims[0] = astate->nelems;
        lbs[0] = 1;
 
-       return makeMdArrayResult(astate, ndims, dims, lbs, rcontext, true);
+       return makeMdArrayResult(astate, ndims, dims, lbs, rcontext,
+                                                        astate->private_cxt);
 }
 
 /*
@@ -4782,6 +4802,11 @@ makeArrayResult(ArrayBuildState *astate,
  * beware: no check that specified dimensions match the number of values
  * accumulated.
  *
+ * Note: if the astate was not initialized within a separate memory context
+ * (that is, initArrayResult was called with subcontext=false), then using
+ * release=true is illegal. Instead, release astate along with the rest of its
+ * context when appropriate.
+ *
  *     astate is working state (must not be NULL)
  *     rcontext is where to construct result
  *     release is true if okay to release working state
@@ -4814,7 +4839,10 @@ makeMdArrayResult(ArrayBuildState *astate,
 
        /* Clean up all the junk */
        if (release)
+       {
+               Assert(astate->private_cxt);
                MemoryContextDelete(astate->mcontext);
+       }
 
        return PointerGetDatum(result);
 }
@@ -4831,26 +4859,42 @@ makeMdArrayResult(ArrayBuildState *astate,
  * initArrayResultArr - initialize an empty ArrayBuildStateArr
  *
  *     array_type is the array type (must be a valid varlena array type)
- *     element_type is the type of the array's elements
+ *     element_type is the type of the array's elements (lookup if InvalidOid)
  *     rcontext is where to keep working state
+ *     subcontext is a flag determining whether to use a separate memory context
  */
 ArrayBuildStateArr *
-initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext)
+initArrayResultArr(Oid array_type, Oid element_type, MemoryContext rcontext,
+                                  bool subcontext)
 {
        ArrayBuildStateArr *astate;
-       MemoryContext arr_context;
+       MemoryContext arr_context = rcontext;   /* by default use the parent ctx */
+
+       /* Lookup element type, unless element_type already provided */
+       if (! OidIsValid(element_type))
+       {
+               element_type = get_element_type(array_type);
+
+               if (!OidIsValid(element_type))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
+                                        errmsg("data type %s is not an array type",
+                                                       format_type_be(array_type))));
+       }
 
        /* Make a temporary context to hold all the junk */
-       arr_context = AllocSetContextCreate(rcontext,
-                                                                               "accumArrayResultArr",
-                                                                               ALLOCSET_DEFAULT_MINSIZE,
-                                                                               ALLOCSET_DEFAULT_INITSIZE,
-                                                                               ALLOCSET_DEFAULT_MAXSIZE);
+       if (subcontext)
+               arr_context = AllocSetContextCreate(rcontext,
+                                                                                       "accumArrayResultArr",
+                                                                                       ALLOCSET_DEFAULT_MINSIZE,
+                                                                                       ALLOCSET_DEFAULT_INITSIZE,
+                                                                                       ALLOCSET_DEFAULT_MAXSIZE);
 
        /* Note we initialize all fields to zero */
        astate = (ArrayBuildStateArr *)
                MemoryContextAllocZero(arr_context, sizeof(ArrayBuildStateArr));
        astate->mcontext = arr_context;
+       astate->private_cxt = subcontext;
 
        /* Save relevant datatype information */
        astate->array_type = array_type;
@@ -4897,21 +4941,9 @@ accumArrayResultArr(ArrayBuildStateArr *astate,
        arg = DatumGetArrayTypeP(dvalue);
 
        if (astate == NULL)
-       {
-               /* First time through --- initialize */
-               Oid                     element_type = get_element_type(array_type);
-
-               if (!OidIsValid(element_type))
-                       ereport(ERROR,
-                                       (errcode(ERRCODE_DATATYPE_MISMATCH),
-                                        errmsg("data type %s is not an array type",
-                                                       format_type_be(array_type))));
-               astate = initArrayResultArr(array_type, element_type, rcontext);
-       }
+               astate = initArrayResultArr(array_type, InvalidOid, rcontext, true);
        else
-       {
                Assert(astate->array_type == array_type);
-       }
 
        oldcontext = MemoryContextSwitchTo(astate->mcontext);
 
@@ -5090,7 +5122,10 @@ makeArrayResultArr(ArrayBuildStateArr *astate,
 
        /* Clean up all the junk */
        if (release)
+       {
+               Assert(astate->private_cxt);
                MemoryContextDelete(astate->mcontext);
+       }
 
        return PointerGetDatum(result);
 }
@@ -5106,9 +5141,10 @@ makeArrayResultArr(ArrayBuildStateArr *astate,
  *
  *     input_type is the input datatype (either element or array type)
  *     rcontext is where to keep working state
+ *     subcontext is a flag determining whether to use a separate memory context
  */
 ArrayBuildStateAny *
-initArrayResultAny(Oid input_type, MemoryContext rcontext)
+initArrayResultAny(Oid input_type, MemoryContext rcontext, bool subcontext)
 {
        ArrayBuildStateAny *astate;
        Oid                     element_type = get_element_type(input_type);
@@ -5118,7 +5154,7 @@ initArrayResultAny(Oid input_type, MemoryContext rcontext)
                /* Array case */
                ArrayBuildStateArr *arraystate;
 
-               arraystate = initArrayResultArr(input_type, element_type, rcontext);
+               arraystate = initArrayResultArr(input_type, InvalidOid, rcontext, subcontext);
                astate = (ArrayBuildStateAny *)
                        MemoryContextAlloc(arraystate->mcontext,
                                                           sizeof(ArrayBuildStateAny));
@@ -5133,7 +5169,7 @@ initArrayResultAny(Oid input_type, MemoryContext rcontext)
                /* Let's just check that we have a type that can be put into arrays */
                Assert(OidIsValid(get_array_type(input_type)));
 
-               scalarstate = initArrayResult(input_type, rcontext);
+               scalarstate = initArrayResult(input_type, rcontext, subcontext);
                astate = (ArrayBuildStateAny *)
                        MemoryContextAlloc(scalarstate->mcontext,
                                                           sizeof(ArrayBuildStateAny));
@@ -5159,7 +5195,7 @@ accumArrayResultAny(ArrayBuildStateAny *astate,
                                        MemoryContext rcontext)
 {
        if (astate == NULL)
-               astate = initArrayResultAny(input_type, rcontext);
+               astate = initArrayResultAny(input_type, rcontext, true);
 
        if (astate->scalarstate)
                (void) accumArrayResult(astate->scalarstate,
index bfe9447295d497077f5652ec899c3f4fc4314e49..8bb7144ecf906653475b689293ff3769d3a46519 100644 (file)
@@ -3948,7 +3948,7 @@ xpath(PG_FUNCTION_ARGS)
        ArrayType  *namespaces = PG_GETARG_ARRAYTYPE_P(2);
        ArrayBuildState *astate;
 
-       astate = initArrayResult(XMLOID, CurrentMemoryContext);
+       astate = initArrayResult(XMLOID, CurrentMemoryContext, true);
        xpath_internal(xpath_expr_text, data, namespaces,
                                   NULL, astate);
        PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext));
index d9fac807f86b0c771d728ca0ac26269fef0a327d..649688ca0a5f447ab287f401d10c9f205ee43333 100644 (file)
@@ -89,6 +89,7 @@ typedef struct ArrayBuildState
        int16           typlen;                 /* needed info about datatype */
        bool            typbyval;
        char            typalign;
+       bool            private_cxt;    /* use private memory context */
 } ArrayBuildState;
 
 /*
@@ -109,6 +110,7 @@ typedef struct ArrayBuildStateArr
        int                     lbs[MAXDIM];
        Oid                     array_type;             /* data type of the arrays */
        Oid                     element_type;   /* data type of the array elements */
+       bool            private_cxt;    /* use private memory context */
 } ArrayBuildStateArr;
 
 /*
@@ -293,7 +295,7 @@ extern void deconstruct_array(ArrayType *array,
 extern bool array_contains_nulls(ArrayType *array);
 
 extern ArrayBuildState *initArrayResult(Oid element_type,
-                               MemoryContext rcontext);
+                               MemoryContext rcontext, bool subcontext);
 extern ArrayBuildState *accumArrayResult(ArrayBuildState *astate,
                                 Datum dvalue, bool disnull,
                                 Oid element_type,
@@ -304,7 +306,7 @@ extern Datum makeMdArrayResult(ArrayBuildState *astate, int ndims,
                                  int *dims, int *lbs, MemoryContext rcontext, bool release);
 
 extern ArrayBuildStateArr *initArrayResultArr(Oid array_type, Oid element_type,
-                                  MemoryContext rcontext);
+                                  MemoryContext rcontext, bool subcontext);
 extern ArrayBuildStateArr *accumArrayResultArr(ArrayBuildStateArr *astate,
                                        Datum dvalue, bool disnull,
                                        Oid array_type,
@@ -313,7 +315,7 @@ extern Datum makeArrayResultArr(ArrayBuildStateArr *astate,
                                   MemoryContext rcontext, bool release);
 
 extern ArrayBuildStateAny *initArrayResultAny(Oid input_type,
-                                  MemoryContext rcontext);
+                                  MemoryContext rcontext, bool subcontext);
 extern ArrayBuildStateAny *accumArrayResultAny(ArrayBuildStateAny *astate,
                                        Datum dvalue, bool disnull,
                                        Oid input_type,
index 492c1ef4d24b93a47fc01396e94f3ab6f30a3f42..e3dda5d63bca5320a4a407790952fdfca141e1ea 100644 (file)
@@ -1218,7 +1218,7 @@ plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
                                 errmsg("cannot convert Perl array to non-array type %s",
                                                format_type_be(typid))));
 
-       astate = initArrayResult(elemtypid, CurrentMemoryContext);
+       astate = initArrayResult(elemtypid, CurrentMemoryContext, true);
 
        _sv_to_datum_finfo(elemtypid, &finfo, &typioparam);