mirror of
https://github.com/postgres/postgres.git
synced 2025-06-26 12:21:12 +03:00
Use a pairing heap for the priority queue in kNN-GiST searches.
This performs slightly better, uses less memory, and needs slightly less code in GiST, than the Red-Black tree previously used. Reviewed by Peter Geoghegan
This commit is contained in:
@ -18,6 +18,7 @@
|
||||
#include "access/relscan.h"
|
||||
#include "miscadmin.h"
|
||||
#include "pgstat.h"
|
||||
#include "lib/pairingheap.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/rel.h"
|
||||
@ -243,8 +244,6 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
|
||||
GISTPageOpaque opaque;
|
||||
OffsetNumber maxoff;
|
||||
OffsetNumber i;
|
||||
GISTSearchTreeItem *tmpItem = so->tmpTreeItem;
|
||||
bool isNew;
|
||||
MemoryContext oldcxt;
|
||||
|
||||
Assert(!GISTSearchItemIsHeap(*pageItem));
|
||||
@ -275,18 +274,15 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
|
||||
oldcxt = MemoryContextSwitchTo(so->queueCxt);
|
||||
|
||||
/* Create new GISTSearchItem for the right sibling index page */
|
||||
item = palloc(sizeof(GISTSearchItem));
|
||||
item->next = NULL;
|
||||
item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
|
||||
item->blkno = opaque->rightlink;
|
||||
item->data.parentlsn = pageItem->data.parentlsn;
|
||||
|
||||
/* Insert it into the queue using same distances as for this page */
|
||||
tmpItem->head = item;
|
||||
tmpItem->lastHeap = NULL;
|
||||
memcpy(tmpItem->distances, myDistances,
|
||||
memcpy(item->distances, myDistances,
|
||||
sizeof(double) * scan->numberOfOrderBys);
|
||||
|
||||
(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
|
||||
pairingheap_add(so->queue, &item->phNode);
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
@ -348,8 +344,7 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
|
||||
oldcxt = MemoryContextSwitchTo(so->queueCxt);
|
||||
|
||||
/* Create new GISTSearchItem for this item */
|
||||
item = palloc(sizeof(GISTSearchItem));
|
||||
item->next = NULL;
|
||||
item = palloc(SizeOfGISTSearchItem(scan->numberOfOrderBys));
|
||||
|
||||
if (GistPageIsLeaf(page))
|
||||
{
|
||||
@ -372,12 +367,10 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
|
||||
}
|
||||
|
||||
/* Insert it into the queue using new distance data */
|
||||
tmpItem->head = item;
|
||||
tmpItem->lastHeap = GISTSearchItemIsHeap(*item) ? item : NULL;
|
||||
memcpy(tmpItem->distances, so->distances,
|
||||
memcpy(item->distances, so->distances,
|
||||
sizeof(double) * scan->numberOfOrderBys);
|
||||
|
||||
(void) rb_insert(so->queue, (RBNode *) tmpItem, &isNew);
|
||||
pairingheap_add(so->queue, &item->phNode);
|
||||
|
||||
MemoryContextSwitchTo(oldcxt);
|
||||
}
|
||||
@ -390,44 +383,24 @@ gistScanPage(IndexScanDesc scan, GISTSearchItem *pageItem, double *myDistances,
|
||||
* Extract next item (in order) from search queue
|
||||
*
|
||||
* Returns a GISTSearchItem or NULL. Caller must pfree item when done with it.
|
||||
*
|
||||
* NOTE: on successful return, so->curTreeItem is the GISTSearchTreeItem that
|
||||
* contained the result item. Callers can use so->curTreeItem->distances as
|
||||
* the distances value for the item.
|
||||
*/
|
||||
static GISTSearchItem *
|
||||
getNextGISTSearchItem(GISTScanOpaque so)
|
||||
{
|
||||
for (;;)
|
||||
GISTSearchItem *item;
|
||||
|
||||
if (!pairingheap_is_empty(so->queue))
|
||||
{
|
||||
GISTSearchItem *item;
|
||||
|
||||
/* Update curTreeItem if we don't have one */
|
||||
if (so->curTreeItem == NULL)
|
||||
{
|
||||
so->curTreeItem = (GISTSearchTreeItem *) rb_leftmost(so->queue);
|
||||
/* Done when tree is empty */
|
||||
if (so->curTreeItem == NULL)
|
||||
break;
|
||||
}
|
||||
|
||||
item = so->curTreeItem->head;
|
||||
if (item != NULL)
|
||||
{
|
||||
/* Delink item from chain */
|
||||
so->curTreeItem->head = item->next;
|
||||
if (item == so->curTreeItem->lastHeap)
|
||||
so->curTreeItem->lastHeap = NULL;
|
||||
/* Return item; caller is responsible to pfree it */
|
||||
return item;
|
||||
}
|
||||
|
||||
/* curTreeItem is exhausted, so remove it from rbtree */
|
||||
rb_delete(so->queue, (RBNode *) so->curTreeItem);
|
||||
so->curTreeItem = NULL;
|
||||
item = (GISTSearchItem *) pairingheap_remove_first(so->queue);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Done when both heaps are empty */
|
||||
item = NULL;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
/* Return item; caller is responsible to pfree it */
|
||||
return item;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -458,7 +431,7 @@ getNextNearest(IndexScanDesc scan)
|
||||
/* visit an index page, extract its items into queue */
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
|
||||
gistScanPage(scan, item, item->distances, NULL, NULL);
|
||||
}
|
||||
|
||||
pfree(item);
|
||||
@ -491,7 +464,6 @@ gistgettuple(PG_FUNCTION_ARGS)
|
||||
pgstat_count_index_scan(scan->indexRelation);
|
||||
|
||||
so->firstCall = false;
|
||||
so->curTreeItem = NULL;
|
||||
so->curPageData = so->nPageData = 0;
|
||||
|
||||
fakeItem.blkno = GIST_ROOT_BLKNO;
|
||||
@ -534,7 +506,7 @@ gistgettuple(PG_FUNCTION_ARGS)
|
||||
* this page, we fall out of the inner "do" and loop around to
|
||||
* return them.
|
||||
*/
|
||||
gistScanPage(scan, item, so->curTreeItem->distances, NULL, NULL);
|
||||
gistScanPage(scan, item, item->distances, NULL, NULL);
|
||||
|
||||
pfree(item);
|
||||
} while (so->nPageData == 0);
|
||||
@ -560,7 +532,6 @@ gistgetbitmap(PG_FUNCTION_ARGS)
|
||||
pgstat_count_index_scan(scan->indexRelation);
|
||||
|
||||
/* Begin the scan by processing the root page */
|
||||
so->curTreeItem = NULL;
|
||||
so->curPageData = so->nPageData = 0;
|
||||
|
||||
fakeItem.blkno = GIST_ROOT_BLKNO;
|
||||
@ -580,7 +551,7 @@ gistgetbitmap(PG_FUNCTION_ARGS)
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
gistScanPage(scan, item, so->curTreeItem->distances, tbm, &ntids);
|
||||
gistScanPage(scan, item, item->distances, tbm, &ntids);
|
||||
|
||||
pfree(item);
|
||||
}
|
||||
|
@ -22,14 +22,13 @@
|
||||
|
||||
|
||||
/*
|
||||
* RBTree support functions for the GISTSearchTreeItem queue
|
||||
* Pairing heap comparison function for the GISTSearchItem queue
|
||||
*/
|
||||
|
||||
static int
|
||||
GISTSearchTreeItemComparator(const RBNode *a, const RBNode *b, void *arg)
|
||||
pairingheap_GISTSearchItem_cmp(const pairingheap_node *a, const pairingheap_node *b, void *arg)
|
||||
{
|
||||
const GISTSearchTreeItem *sa = (const GISTSearchTreeItem *) a;
|
||||
const GISTSearchTreeItem *sb = (const GISTSearchTreeItem *) b;
|
||||
const GISTSearchItem *sa = (const GISTSearchItem *) a;
|
||||
const GISTSearchItem *sb = (const GISTSearchItem *) b;
|
||||
IndexScanDesc scan = (IndexScanDesc) arg;
|
||||
int i;
|
||||
|
||||
@ -37,61 +36,18 @@ GISTSearchTreeItemComparator(const RBNode *a, const RBNode *b, void *arg)
|
||||
for (i = 0; i < scan->numberOfOrderBys; i++)
|
||||
{
|
||||
if (sa->distances[i] != sb->distances[i])
|
||||
return (sa->distances[i] > sb->distances[i]) ? 1 : -1;
|
||||
return (sa->distances[i] < sb->distances[i]) ? 1 : -1;
|
||||
}
|
||||
|
||||
/* Heap items go before inner pages, to ensure a depth-first search */
|
||||
if (GISTSearchItemIsHeap(*sa) && !GISTSearchItemIsHeap(*sb))
|
||||
return -1;
|
||||
if (!GISTSearchItemIsHeap(*sa) && GISTSearchItemIsHeap(*sb))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
GISTSearchTreeItemCombiner(RBNode *existing, const RBNode *newrb, void *arg)
|
||||
{
|
||||
GISTSearchTreeItem *scurrent = (GISTSearchTreeItem *) existing;
|
||||
const GISTSearchTreeItem *snew = (const GISTSearchTreeItem *) newrb;
|
||||
GISTSearchItem *newitem = snew->head;
|
||||
|
||||
/* snew should have just one item in its chain */
|
||||
Assert(newitem && newitem->next == NULL);
|
||||
|
||||
/*
|
||||
* If new item is heap tuple, it goes to front of chain; otherwise insert
|
||||
* it before the first index-page item, so that index pages are visited in
|
||||
* LIFO order, ensuring depth-first search of index pages. See comments
|
||||
* in gist_private.h.
|
||||
*/
|
||||
if (GISTSearchItemIsHeap(*newitem))
|
||||
{
|
||||
newitem->next = scurrent->head;
|
||||
scurrent->head = newitem;
|
||||
if (scurrent->lastHeap == NULL)
|
||||
scurrent->lastHeap = newitem;
|
||||
}
|
||||
else if (scurrent->lastHeap == NULL)
|
||||
{
|
||||
newitem->next = scurrent->head;
|
||||
scurrent->head = newitem;
|
||||
}
|
||||
else
|
||||
{
|
||||
newitem->next = scurrent->lastHeap->next;
|
||||
scurrent->lastHeap->next = newitem;
|
||||
}
|
||||
}
|
||||
|
||||
static RBNode *
|
||||
GISTSearchTreeItemAllocator(void *arg)
|
||||
{
|
||||
IndexScanDesc scan = (IndexScanDesc) arg;
|
||||
|
||||
return palloc(GSTIHDRSZ + sizeof(double) * scan->numberOfOrderBys);
|
||||
}
|
||||
|
||||
static void
|
||||
GISTSearchTreeItemDeleter(RBNode *rb, void *arg)
|
||||
{
|
||||
pfree(rb);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Index AM API functions for scanning GiST indexes
|
||||
@ -127,7 +83,6 @@ gistbeginscan(PG_FUNCTION_ARGS)
|
||||
so->queueCxt = giststate->scanCxt; /* see gistrescan */
|
||||
|
||||
/* workspaces with size dependent on numberOfOrderBys: */
|
||||
so->tmpTreeItem = palloc(GSTIHDRSZ + sizeof(double) * scan->numberOfOrderBys);
|
||||
so->distances = palloc(sizeof(double) * scan->numberOfOrderBys);
|
||||
so->qual_ok = true; /* in case there are zero keys */
|
||||
|
||||
@ -188,15 +143,9 @@ gistrescan(PG_FUNCTION_ARGS)
|
||||
|
||||
/* create new, empty RBTree for search queue */
|
||||
oldCxt = MemoryContextSwitchTo(so->queueCxt);
|
||||
so->queue = rb_create(GSTIHDRSZ + sizeof(double) * scan->numberOfOrderBys,
|
||||
GISTSearchTreeItemComparator,
|
||||
GISTSearchTreeItemCombiner,
|
||||
GISTSearchTreeItemAllocator,
|
||||
GISTSearchTreeItemDeleter,
|
||||
scan);
|
||||
so->queue = pairingheap_allocate(pairingheap_GISTSearchItem_cmp, scan);
|
||||
MemoryContextSwitchTo(oldCxt);
|
||||
|
||||
so->curTreeItem = NULL;
|
||||
so->firstCall = true;
|
||||
|
||||
/* Update scan key, if a new one is given */
|
||||
|
Reference in New Issue
Block a user