Logical Tape Set: use min heap for freelist.

Previously, the freelist of blocks was tracked as an
occasionally-sorted array. A min heap is more resilient to larger
freelists or more frequent changes between reading and writing.

Discussion: https://postgr.es/m/97c46a59c27f3c38e486ca170fcbc618d97ab049.camel%40j-davis.com
This commit is contained in:
Jeff Davis 2020-02-06 10:06:25 -08:00
parent cac8ce4a73
commit c02fdc9223
1 changed files with 104 additions and 56 deletions

View File

@ -49,12 +49,8 @@
* when reading, and read multiple blocks from the same tape in one go, * when reading, and read multiple blocks from the same tape in one go,
* whenever the buffer becomes empty. * whenever the buffer becomes empty.
* *
* To support the above policy of writing to the lowest free block, * To support the above policy of writing to the lowest free block, the
* ltsGetFreeBlock sorts the list of free block numbers into decreasing * freelist is a min heap.
* order each time it is asked for a block and the list isn't currently
* sorted. This is an efficient way to handle it because we expect cycles
* of releasing many blocks followed by re-using many blocks, due to
* the larger read buffer.
* *
* Since all the bookkeeping and buffer memory is allocated with palloc(), * Since all the bookkeeping and buffer memory is allocated with palloc(),
* and the underlying file(s) are made with OpenTemporaryFile, all resources * and the underlying file(s) are made with OpenTemporaryFile, all resources
@ -170,7 +166,7 @@ struct LogicalTapeSet
/* /*
* File size tracking. nBlocksWritten is the size of the underlying file, * File size tracking. nBlocksWritten is the size of the underlying file,
* in BLCKSZ blocks. nBlocksAllocated is the number of blocks allocated * in BLCKSZ blocks. nBlocksAllocated is the number of blocks allocated
* by ltsGetFreeBlock(), and it is always greater than or equal to * by ltsReleaseBlock(), and it is always greater than or equal to
* nBlocksWritten. Blocks between nBlocksAllocated and nBlocksWritten are * nBlocksWritten. Blocks between nBlocksAllocated and nBlocksWritten are
* blocks that have been allocated for a tape, but have not been written * blocks that have been allocated for a tape, but have not been written
* to the underlying file yet. nHoleBlocks tracks the total number of * to the underlying file yet. nHoleBlocks tracks the total number of
@ -188,17 +184,11 @@ struct LogicalTapeSet
* If forgetFreeSpace is true then any freed blocks are simply forgotten * If forgetFreeSpace is true then any freed blocks are simply forgotten
* rather than being remembered in freeBlocks[]. See notes for * rather than being remembered in freeBlocks[]. See notes for
* LogicalTapeSetForgetFreeSpace(). * LogicalTapeSetForgetFreeSpace().
*
* If blocksSorted is true then the block numbers in freeBlocks are in
* *decreasing* order, so that removing the last entry gives us the lowest
* free block. We re-sort the blocks whenever a block is demanded; this
* should be reasonably efficient given the expected usage pattern.
*/ */
bool forgetFreeSpace; /* are we remembering free blocks? */ bool forgetFreeSpace; /* are we remembering free blocks? */
bool blocksSorted; /* is freeBlocks[] currently in order? */ long *freeBlocks; /* resizable array holding minheap */
long *freeBlocks; /* resizable array */ long nFreeBlocks; /* # of currently free blocks */
int nFreeBlocks; /* # of currently free blocks */ Size freeBlocksLen; /* current allocated length of freeBlocks[] */
int freeBlocksLen; /* current allocated length of freeBlocks[] */
/* The array of logical tapes. */ /* The array of logical tapes. */
int nTapes; /* # of logical tapes in set */ int nTapes; /* # of logical tapes in set */
@ -321,46 +311,88 @@ ltsReadFillBuffer(LogicalTapeSet *lts, LogicalTape *lt)
return (lt->nbytes > 0); return (lt->nbytes > 0);
} }
/* static inline void
* qsort comparator for sorting freeBlocks[] into decreasing order. swap_nodes(long *heap, unsigned long a, unsigned long b)
*/
static int
freeBlocks_cmp(const void *a, const void *b)
{ {
long ablk = *((const long *) a); unsigned long swap;
long bblk = *((const long *) b);
/* can't just subtract because long might be wider than int */ swap = heap[a];
if (ablk < bblk) heap[a] = heap[b];
return 1; heap[b] = swap;
if (ablk > bblk) }
return -1;
return 0; static inline unsigned long
left_offset(unsigned long i)
{
return 2 * i + 1;
}
static inline unsigned long
right_offset(unsigned i)
{
return 2 * i + 2;
}
static inline unsigned long
parent_offset(unsigned long i)
{
return (i - 1) / 2;
} }
/* /*
* Select a currently unused block for writing to. * Select the lowest currently unused block by taking the first element from
* the freelist min heap.
*/ */
static long static long
ltsGetFreeBlock(LogicalTapeSet *lts) ltsGetFreeBlock(LogicalTapeSet *lts)
{ {
/* long *heap = lts->freeBlocks;
* If there are multiple free blocks, we select the one appearing last in long blocknum;
* freeBlocks[] (after sorting the array if needed). If there are none, int heapsize;
* assign the next block at the end of the file. unsigned long pos;
*/
if (lts->nFreeBlocks > 0) /* freelist empty; allocate a new block */
{ if (lts->nFreeBlocks == 0)
if (!lts->blocksSorted)
{
qsort((void *) lts->freeBlocks, lts->nFreeBlocks,
sizeof(long), freeBlocks_cmp);
lts->blocksSorted = true;
}
return lts->freeBlocks[--lts->nFreeBlocks];
}
else
return lts->nBlocksAllocated++; return lts->nBlocksAllocated++;
if (lts->nFreeBlocks == 1)
{
lts->nFreeBlocks--;
return lts->freeBlocks[0];
}
/* take top of minheap */
blocknum = heap[0];
/* replace with end of minheap array */
heap[0] = heap[--lts->nFreeBlocks];
/* sift down */
pos = 0;
heapsize = lts->nFreeBlocks;
while (true)
{
unsigned long left = left_offset(pos);
unsigned long right = right_offset(pos);
unsigned long min_child;
if (left < heapsize && right < heapsize)
min_child = (heap[left] < heap[right]) ? left : right;
else if (left < heapsize)
min_child = left;
else if (right < heapsize)
min_child = right;
else
break;
if (heap[min_child] >= heap[pos])
break;
swap_nodes(heap, min_child, pos);
pos = min_child;
}
return blocknum;
} }
/* /*
@ -369,7 +401,8 @@ ltsGetFreeBlock(LogicalTapeSet *lts)
static void static void
ltsReleaseBlock(LogicalTapeSet *lts, long blocknum) ltsReleaseBlock(LogicalTapeSet *lts, long blocknum)
{ {
int ndx; long *heap;
unsigned long pos;
/* /*
* Do nothing if we're no longer interested in remembering free space. * Do nothing if we're no longer interested in remembering free space.
@ -382,19 +415,35 @@ ltsReleaseBlock(LogicalTapeSet *lts, long blocknum)
*/ */
if (lts->nFreeBlocks >= lts->freeBlocksLen) if (lts->nFreeBlocks >= lts->freeBlocksLen)
{ {
/*
* If the freelist becomes very large, just return and leak this free
* block.
*/
if (lts->freeBlocksLen * 2 > MaxAllocSize)
return;
lts->freeBlocksLen *= 2; lts->freeBlocksLen *= 2;
lts->freeBlocks = (long *) repalloc(lts->freeBlocks, lts->freeBlocks = (long *) repalloc(lts->freeBlocks,
lts->freeBlocksLen * sizeof(long)); lts->freeBlocksLen * sizeof(long));
} }
/* heap = lts->freeBlocks;
* Add blocknum to array, and mark the array unsorted if it's no longer in pos = lts->nFreeBlocks;
* decreasing order.
*/ /* place entry at end of minheap array */
ndx = lts->nFreeBlocks++; heap[pos] = blocknum;
lts->freeBlocks[ndx] = blocknum; lts->nFreeBlocks++;
if (ndx > 0 && lts->freeBlocks[ndx - 1] < blocknum)
lts->blocksSorted = false; /* sift up */
while (pos != 0)
{
unsigned long parent = parent_offset(pos);
if (heap[parent] < heap[pos])
break;
swap_nodes(heap, parent, pos);
pos = parent;
}
} }
/* /*
@ -524,7 +573,6 @@ LogicalTapeSetCreate(int ntapes, TapeShare *shared, SharedFileSet *fileset,
lts->nBlocksWritten = 0L; lts->nBlocksWritten = 0L;
lts->nHoleBlocks = 0L; lts->nHoleBlocks = 0L;
lts->forgetFreeSpace = false; lts->forgetFreeSpace = false;
lts->blocksSorted = true; /* a zero-length array is sorted ... */
lts->freeBlocksLen = 32; /* reasonable initial guess */ lts->freeBlocksLen = 32; /* reasonable initial guess */
lts->freeBlocks = (long *) palloc(lts->freeBlocksLen * sizeof(long)); lts->freeBlocks = (long *) palloc(lts->freeBlocksLen * sizeof(long));
lts->nFreeBlocks = 0; lts->nFreeBlocks = 0;