@@ -105,6 +105,7 @@ struct Tuplestorestate
105
105
bool interXact ; /* keep open through transactions? */
106
106
bool truncated ; /* tuplestore_trim has removed tuples? */
107
107
long availMem ; /* remaining memory available, in bytes */
108
+ long allowedMem ; /* total memory allowed, in bytes */
108
109
BufFile * myfile ; /* underlying file, or NULL if none */
109
110
MemoryContext context ; /* memory context for holding tuples */
110
111
ResourceOwner resowner ; /* resowner for holding temp files */
@@ -156,6 +157,7 @@ struct Tuplestorestate
156
157
int memtupdeleted ; /* the first N slots are currently unused */
157
158
int memtupcount ; /* number of tuples currently present */
158
159
int memtupsize ; /* allocated length of memtuples array */
160
+ bool growmemtuples ; /* memtuples' growth still underway? */
159
161
160
162
/*
161
163
* These variables are used to keep track of the current positions.
@@ -254,14 +256,16 @@ tuplestore_begin_common(int eflags, bool interXact, int maxKBytes)
254
256
state -> eflags = eflags ;
255
257
state -> interXact = interXact ;
256
258
state -> truncated = false;
257
- state -> availMem = maxKBytes * 1024L ;
259
+ state -> allowedMem = maxKBytes * 1024L ;
260
+ state -> availMem = state -> allowedMem ;
258
261
state -> myfile = NULL ;
259
262
state -> context = CurrentMemoryContext ;
260
263
state -> resowner = CurrentResourceOwner ;
261
264
262
265
state -> memtupdeleted = 0 ;
263
266
state -> memtupcount = 0 ;
264
267
state -> memtupsize = 1024 ; /* initial guess */
268
+ state -> growmemtuples = true;
265
269
state -> memtuples = (void * * ) palloc (state -> memtupsize * sizeof (void * ));
266
270
267
271
USEMEM (state , GetMemoryChunkSpace (state -> memtuples ));
@@ -526,6 +530,126 @@ tuplestore_ateof(Tuplestorestate *state)
526
530
return state -> readptrs [state -> activeptr ].eof_reached ;
527
531
}
528
532
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
+
529
653
/*
530
654
* Accept one tuple and append it to the tuplestore.
531
655
*
@@ -631,20 +755,8 @@ tuplestore_puttuple_common(Tuplestorestate *state, void *tuple)
631
755
*/
632
756
if (state -> memtupcount >= state -> memtupsize - 1 )
633
757
{
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 );
648
760
}
649
761
650
762
/* Stash the tuple in the in-memory array */
0 commit comments