mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-30 16:51:14 +02:00
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:
parent
cac8ce4a73
commit
c02fdc9223
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user