@@ -105,6 +105,7 @@ struct Tuplestorestate
105105 bool interXact ; /* keep open through transactions? */
106106 bool truncated ; /* tuplestore_trim has removed tuples? */
107107 long availMem ; /* remaining memory available, in bytes */
108+ long allowedMem ; /* total memory allowed, in bytes */
108109 BufFile * myfile ; /* underlying file, or NULL if none */
109110 MemoryContext context ; /* memory context for holding tuples */
110111 ResourceOwner resowner ; /* resowner for holding temp files */
@@ -156,6 +157,7 @@ struct Tuplestorestate
156157 int memtupdeleted ; /* the first N slots are currently unused */
157158 int memtupcount ; /* number of tuples currently present */
158159 int memtupsize ; /* allocated length of memtuples array */
160+ bool growmemtuples ; /* memtuples' growth still underway? */
159161
160162 /*
161163 * These variables are used to keep track of the current positions.
@@ -254,14 +256,16 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
254256 state -> eflags = eflags ;
255257 state -> interXact = interXact ;
256258 state -> truncated = false;
257- state -> availMem = maxKBytes * 1024L ;
259+ state -> allowedMem = maxKBytes * 1024L ;
260+ state -> availMem = state -> allowedMem ;
258261 state -> myfile = NULL ;
259262 state -> context = CurrentMemoryContext ;
260263 state -> resowner = CurrentResourceOwner ;
261264
262265 state -> memtupdeleted = 0 ;
263266 state -> memtupcount = 0 ;
264267 state -> memtupsize = 1024 ; /* initial guess */
268+ state -> growmemtuples = true;
265269 state -> memtuples = (void * * ) palloc (state -> memtupsize * sizeof (void * ));
266270
267271 USEMEM (state , GetMemoryChunkSpace (state -> memtuples ));
@@ -526,6 +530,126 @@ tuplestore_ateof(Tuplestorestate *state)
526530 return state -> readptrs [state -> activeptr ].eof_reached ;
527531}
528532
533+ /*
534+ * Grow the memtuples[] array, if possible within our memory constraint.
535+ * Return TRUE if we were able to enlarge the array, FALSE if not.
536+ *
537+ * Normally, at each increment we double the size of the array. When we no
538+ * longer have enough memory to do that, we attempt one last, smaller increase
539+ * (and then clear the growmemtuples flag so we don't try any more). That
540+ * allows us to use allowedMem as fully as possible; sticking to the pure
541+ * doubling rule could result in almost half of allowedMem going unused.
542+ * Because availMem moves around with tuple addition/removal, we need some
543+ * rule to prevent making repeated small increases in memtupsize, which would
544+ * just be useless thrashing. The growmemtuples flag accomplishes that and
545+ * also prevents useless recalculations in this function.
546+ */
547+ static bool
548+ grow_memtuples (Tuplestorestate * state )
549+ {
550+ int newmemtupsize ;
551+ int memtupsize = state -> memtupsize ;
552+ long memNowUsed = state -> allowedMem - state -> availMem ;
553+
554+ /* Forget it if we've already maxed out memtuples, per comment above */
555+ if (!state -> growmemtuples )
556+ return false;
557+
558+ /* Select new value of memtupsize */
559+ if (memNowUsed <= state -> availMem )
560+ {
561+ /*
562+ * It is surely safe to double memtupsize if we've used no more than
563+ * half of allowedMem.
564+ *
565+ * Note: it might seem that we need to worry about memtupsize * 2
566+ * overflowing an int, but the MaxAllocSize clamp applied below
567+ * ensures the existing memtupsize can't be large enough for that.
568+ */
569+ newmemtupsize = memtupsize * 2 ;
570+ }
571+ else
572+ {
573+ /*
574+ * This will be the last increment of memtupsize. Abandon doubling
575+ * strategy and instead increase as much as we safely can.
576+ *
577+ * To stay within allowedMem, we can't increase memtupsize by more
578+ * than availMem / sizeof(void *) elements. In practice, we want
579+ * to increase it by considerably less, because we need to leave some
580+ * space for the tuples to which the new array slots will refer. We
581+ * assume the new tuples will be about the same size as the tuples
582+ * we've already seen, and thus we can extrapolate from the space
583+ * consumption so far to estimate an appropriate new size for the
584+ * memtuples array. The optimal value might be higher or lower than
585+ * this estimate, but it's hard to know that in advance.
586+ *
587+ * This calculation is safe against enlarging the array so much that
588+ * LACKMEM becomes true, because the memory currently used includes
589+ * the present array; thus, there would be enough allowedMem for the
590+ * new array elements even if no other memory were currently used.
591+ *
592+ * We do the arithmetic in float8, because otherwise the product of
593+ * memtupsize and allowedMem could overflow. (A little algebra shows
594+ * that grow_ratio must be less than 2 here, so we are not risking
595+ * integer overflow this way.) Any inaccuracy in the result should be
596+ * insignificant; but even if we computed a completely insane result,
597+ * the checks below will prevent anything really bad from happening.
598+ */
599+ double grow_ratio ;
600+
601+ grow_ratio = (double ) state -> allowedMem / (double ) memNowUsed ;
602+ newmemtupsize = (int ) (memtupsize * grow_ratio );
603+
604+ /* We won't make any further enlargement attempts */
605+ state -> growmemtuples = false;
606+ }
607+
608+ /* Must enlarge array by at least one element, else report failure */
609+ if (newmemtupsize <= memtupsize )
610+ goto noalloc ;
611+
612+ /*
613+ * On a 64-bit machine, allowedMem could be more than MaxAllocSize. Clamp
614+ * to ensure our request won't be rejected by palloc.
615+ */
616+ if ((Size ) newmemtupsize >= MaxAllocSize / sizeof (void * ))
617+ {
618+ newmemtupsize = (int ) (MaxAllocSize / sizeof (void * ));
619+ state -> growmemtuples = false; /* can't grow any more */
620+ }
621+
622+ /*
623+ * We need to be sure that we do not cause LACKMEM to become true, else
624+ * the space management algorithm will go nuts. The code above should
625+ * never generate a dangerous request, but to be safe, check explicitly
626+ * that the array growth fits within availMem. (We could still cause
627+ * LACKMEM if the memory chunk overhead associated with the memtuples
628+ * array were to increase. That shouldn't happen with any sane value of
629+ * allowedMem, because at any array size large enough to risk LACKMEM,
630+ * palloc would be treating both old and new arrays as separate chunks.
631+ * But we'll check LACKMEM explicitly below just in case.)
632+ */
633+ if (state -> availMem < (long ) ((newmemtupsize - memtupsize ) * sizeof (void * )))
634+ goto noalloc ;
635+
636+ /* OK, do it */
637+ FREEMEM (state , GetMemoryChunkSpace (state -> memtuples ));
638+ state -> memtupsize = newmemtupsize ;
639+ state -> memtuples = (void * * )
640+ repalloc (state -> memtuples ,
641+ state -> memtupsize * sizeof (void * ));
642+ USEMEM (state , GetMemoryChunkSpace (state -> memtuples ));
643+ if (LACKMEM (state ))
644+ elog (ERROR , "unexpected out-of-memory situation during sort" );
645+ return true;
646+
647+ noalloc :
648+ /* If for any reason we didn't realloc, shut off future attempts */
649+ state -> growmemtuples = false;
650+ return false;
651+ }
652+
529653/*
530654 * Accept one tuple and append it to the tuplestore.
531655 *
@@ -631,20 +755,8 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
631755 */
632756 if (state -> memtupcount >= state -> memtupsize - 1 )
633757 {
634- /*
635- * See grow_memtuples() in tuplesort.c for the rationale
636- * behind these two tests.
637- */
638- if (state -> availMem > (long ) (state -> memtupsize * sizeof (void * )) &&
639- (Size ) (state -> memtupsize * 2 ) < MaxAllocSize / sizeof (void * ))
640- {
641- FREEMEM (state , GetMemoryChunkSpace (state -> memtuples ));
642- state -> memtupsize *= 2 ;
643- state -> memtuples = (void * * )
644- repalloc (state -> memtuples ,
645- state -> memtupsize * sizeof (void * ));
646- USEMEM (state , GetMemoryChunkSpace (state -> memtuples ));
647- }
758+ (void ) grow_memtuples (state );
759+ Assert (state -> memtupcount < state -> memtupsize );
648760 }
649761
650762 /* Stash the tuple in the in-memory array */
0 commit comments