Re: rtree improvements - Mailing list pgsql-patches
From | Bruce Momjian |
---|---|
Subject | Re: rtree improvements |
Date | |
Msg-id | 200109261627.f8QGRDD26488@candle.pha.pa.us Whole thread Raw |
In response to | rtree improvements (Kenneth Been <kennethb@telocity.com>) |
List | pgsql-patches |
Your patch has been added to the PostgreSQL unapplied patches list at: http://candle.pha.pa.us/cgi-bin/pgpatches I will try to apply it within the next 48 hours. > I have made three changes to the rtree code: one bug fix and > two performance improvements. I put an explanation of the > changes at > > http://cs1.cs.nyu.edu/been/postgres-rtree.html > > The performance improvements are quite significant. > > All the changes are in the file src/backend/access/rtree/rtree.c > > I was working with the 7.1.3 code. > > I'm including the diff output as an attachment. > > Ken > > diff -Naur postgresql-7.1.3.old/src/backend/access/rtree/rtree.c postgresql-7.1.3/src/backend/access/rtree/rtree.c > --- postgresql-7.1.3.old/src/backend/access/rtree/rtree.c Wed Mar 21 22:59:16 2001 > +++ postgresql-7.1.3/src/backend/access/rtree/rtree.c Tue Sep 25 13:56:31 2001 > @@ -55,6 +55,14 @@ > Datum spl_rdatum; > } SPLITVEC; > > +/* for sorting tuples by cost, for picking split */ > +typedef struct SPLITCOST > +{ > + OffsetNumber offset_number; > + float cost_differential; > + bool choose_left; > +} SPLITCOST; > + > typedef struct RTSTATE > { > FmgrInfo unionFn; /* union function */ > @@ -79,6 +87,7 @@ > RTSTATE *rtstate); > static int nospace(Page p, IndexTuple it); > static void initRtstate(RTSTATE *rtstate, Relation index); > +static int qsort_comp_splitcost(const void *a, const void *b); > > > Datum > @@ -427,7 +436,12 @@ > FunctionCall2(&rtstate->sizeFn, datum, > PointerGetDatum(&newd_size)); > > - if (newd_size != old_size) > + /* > + * If newd_size == 0 we have degenerate rectangles, so we > + * don't know if there was any change, so we have to > + * assume there was. > + */ > + if ((newd_size == 0) || (newd_size != old_size)) > { > TupleDesc td = RelationGetDescr(r); > > @@ -503,6 +517,8 @@ > OffsetNumber *spl_left, > *spl_right; > TupleDesc tupDesc; > + int n; > + OffsetNumber newitemoff; > > p = (Page) BufferGetPage(buffer); > opaque = (RTreePageOpaque) PageGetSpecialPointer(p); > @@ -539,56 +555,64 @@ > spl_right = v.spl_right; > leftoff = rightoff = FirstOffsetNumber; > maxoff = PageGetMaxOffsetNumber(p); > - for (i = FirstOffsetNumber; i <= maxoff; i = OffsetNumberNext(i)) > - { > - itemid = PageGetItemId(p, i); > - item = (IndexTuple) PageGetItem(p, itemid); > - > - if (i == *spl_left) > - { > - if (PageAddItem(left, (Item) item, IndexTupleSize(item), > - leftoff, LP_USED) == InvalidOffsetNumber) > - elog(ERROR, "rtdosplit: failed to copy index item in %s", > - RelationGetRelationName(r)); > - leftoff = OffsetNumberNext(leftoff); > - spl_left++; /* advance in left split vector */ > - } > - else > - { > - Assert(i == *spl_right); > - if (PageAddItem(right, (Item) item, IndexTupleSize(item), > - rightoff, LP_USED) == InvalidOffsetNumber) > - elog(ERROR, "rtdosplit: failed to copy index item in %s", > - RelationGetRelationName(r)); > - rightoff = OffsetNumberNext(rightoff); > - spl_right++; /* advance in right split vector */ > - } > - } > + newitemoff = OffsetNumberNext(maxoff); > > /* build an InsertIndexResult for this insertion */ > res = (InsertIndexResult) palloc(sizeof(InsertIndexResultData)); > > - /* now insert the new index tuple */ > - if (*spl_left == maxoff + 1) > + /* > + * spl_left contains a list of the offset numbers of the > + * tuples that will go to the left page. For each offset > + * number, get the tuple item, then add the item to the > + * left page. Similarly for the right side. > + */ > + > + /* fill left node */ > + for (n = 0; n < v.spl_nleft; n++) > { > - if (PageAddItem(left, (Item) itup, IndexTupleSize(itup), > + i = *spl_left; > + if (i == newitemoff) > + item = itup; > + else > + { > + itemid = PageGetItemId(p, i); > + item = (IndexTuple) PageGetItem(p, itemid); > + } > + > + if (PageAddItem(left, (Item) item, IndexTupleSize(item), > leftoff, LP_USED) == InvalidOffsetNumber) > elog(ERROR, "rtdosplit: failed to add index item to %s", > RelationGetRelationName(r)); > leftoff = OffsetNumberNext(leftoff); > - ItemPointerSet(&(res->pointerData), lbknum, leftoff); > - spl_left++; > + > + if (i == newitemoff) > + ItemPointerSet(&(res->pointerData), lbknum, leftoff); > + > + spl_left++; /* advance in left split vector */ > } > - else > + > + /* fill right node */ > + for (n = 0; n < v.spl_nright; n++) > { > - Assert(*spl_right == maxoff + 1); > - if (PageAddItem(right, (Item) itup, IndexTupleSize(itup), > + i = *spl_right; > + if (i == newitemoff) > + item = itup; > + else > + { > + itemid = PageGetItemId(p, i); > + item = (IndexTuple) PageGetItem(p, itemid); > + } > + > + if (PageAddItem(right, (Item) item, IndexTupleSize(item), > rightoff, LP_USED) == InvalidOffsetNumber) > elog(ERROR, "rtdosplit: failed to add index item to %s", > RelationGetRelationName(r)); > rightoff = OffsetNumberNext(rightoff); > - ItemPointerSet(&(res->pointerData), rbknum, rightoff); > - spl_right++; > + > + if (i == newitemoff) > + ItemPointerSet(&(res->pointerData), rbknum, rightoff); > + > + spl_right++; /* advance in right split vector */ > } > > /* Make sure we consumed all of the split vectors, and release 'em */ > @@ -741,8 +765,10 @@ > * In addition, the item to be added (itup) is listed in the appropriate > * vector. It is represented by item number N+1 (N = # of items on page). > * > - * Both vectors appear in sequence order with a terminating sentinel value > - * of InvalidOffsetNumber. > + * Both vectors have a terminating sentinel value of InvalidOffsetNumber, > + * but the sentinal value is no longer used, because the SPLITVEC > + * vector also contains the length of each vector, and that information > + * is now used to iterate over them in rtdosplit(). --kbb, 21 Sept 2001 > * > * The bounding-box datums for the two new pages are also returned in *v. > * > @@ -797,6 +823,12 @@ > item_2_sz, > left_avail_space, > right_avail_space; > + int total_num_tuples, > + num_tuples_without_seeds, > + max_after_split; /* in Guttman's lingo, (M - m) */ > + float diff; /* diff between cost of putting tuple left or right */ > + SPLITCOST *cost_vector; > + int n; > > /* > * First, make sure the new item is not so large that we can't > @@ -812,6 +844,9 @@ > maxoff = PageGetMaxOffsetNumber(page); > newitemoff = OffsetNumberNext(maxoff); /* phony index for new > * item */ > + total_num_tuples = newitemoff; > + num_tuples_without_seeds = total_num_tuples - 2; > + max_after_split = total_num_tuples / 2; /* works for m = M/2 */ > > /* Make arrays big enough for worst case, including sentinel */ > nbytes = (maxoff + 2) * sizeof(OffsetNumber); > @@ -909,47 +944,111 @@ > right_avail_space = RTPageAvailSpace - IndexTupleTotalSize(item_2); > > /* > - * Now split up the regions between the two seeds. An important > - * property of this split algorithm is that the split vector v has the > - * indices of items to be split in order in its left and right > - * vectors. We exploit this property by doing a merge in the code > - * that actually splits the page. > + * Now split up the regions between the two seeds. > + * > + * The cost_vector array will contain hints for determining where > + * each tuple should go. Each record in the array will contain > + * a boolean, choose_left, that indicates which node the tuple > + * prefers to be on, and the absolute difference in cost between > + * putting the tuple in its favored node and in the other node. > * > - * For efficiency, we also place the new index tuple in this loop. This > - * is handled at the very end, when we have placed all the existing > - * tuples and i == maxoff + 1. > + * Later, we will sort the cost_vector in descending order by cost > + * difference, and consider the tuples in that order for > + * placement. That way, the tuples that *really* want to be in > + * one node or the other get to choose first, and the tuples that > + * don't really care choose last. > + * > + * First, build the cost_vector array. The new index tuple will > + * also be handled in this loop, and represented in the array, > + * with i==newitemoff. > + * > + * In the case of variable size tuples it is possible that we only > + * have the two seeds and no other tuples, in which case we don't > + * do any of this cost_vector stuff. > */ > + > + /* to keep compiler quiet */ > + cost_vector = (SPLITCOST *) NULL; > + > + if (num_tuples_without_seeds > 0) > + { > + cost_vector = > + (SPLITCOST *) palloc(num_tuples_without_seeds * sizeof(SPLITCOST)); > + n = 0; > + for (i = FirstOffsetNumber; i <= newitemoff; i = OffsetNumberNext(i)) > + { > + /* Compute new union datums and sizes for both choices */ > + > + if ((i == seed_1) || (i == seed_2)) > + continue; > + else if (i == newitemoff) > + item_1 = itup; > + else > + item_1 = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); > + > + datum_alpha = IndexTupleGetDatum(item_1); > + union_dl = FunctionCall2(&rtstate->unionFn, datum_l, datum_alpha); > + union_dr = FunctionCall2(&rtstate->unionFn, datum_r, datum_alpha); > + FunctionCall2(&rtstate->sizeFn, union_dl, > + PointerGetDatum(&size_alpha)); > + FunctionCall2(&rtstate->sizeFn, union_dr, > + PointerGetDatum(&size_beta)); > + > + diff = (size_alpha - size_l) - (size_beta - size_r); > + > + cost_vector[n].offset_number = i; > + cost_vector[n].cost_differential = fabs(diff); > + cost_vector[n].choose_left = (diff < 0); > + > + n++; > + } > + > + /* > + * Sort the array. The function qsort_comp_splitcost is > + * set up "backwards", to provided descending order. > + */ > + qsort(cost_vector, num_tuples_without_seeds, sizeof(SPLITCOST), > + &qsort_comp_splitcost); > + } > + > + /* > + * Now make the final decisions about where each tuple will go, > + * and build the vectors to return in the SPLITVEC record. > + * > + * The cost_vector array contains (descriptions of) all the > + * tuples, in the order that we want to consider them, so we > + * we just iterate through it and place each tuple in left > + * or right nodes, according to the criteria described below. > + */ > + > left = v->spl_left; > v->spl_nleft = 0; > right = v->spl_right; > v->spl_nright = 0; > > - for (i = FirstOffsetNumber; i <= newitemoff; i = OffsetNumberNext(i)) > + /* Place the seeds first. > + * left avail space, left union, right avail space, and right > + * union have already been adjusted for the seeds. > + */ > + > + *left++ = seed_1; > + v->spl_nleft++; > + > + *right++ = seed_2; > + v->spl_nright++; > + > + for (n = 0; n < num_tuples_without_seeds; n++) > { > bool left_feasible, > right_feasible, > choose_left; > > /* > - * If we've already decided where to place this item, just put it > - * on the correct list. Otherwise, we need to figure out which > - * page needs the least enlargement in order to store the item. > + * We need to figure out which page needs the least > + * enlargement in order to store the item. > */ > > - if (i == seed_1) > - { > - *left++ = i; > - v->spl_nleft++; > - /* left avail_space & union already includes this one */ > - continue; > - } > - if (i == seed_2) > - { > - *right++ = i; > - v->spl_nright++; > - /* right avail_space & union already includes this one */ > - continue; > - } > + i = cost_vector[n].offset_number; > > /* Compute new union datums and sizes for both possible additions */ > if (i == newitemoff) > @@ -979,6 +1078,24 @@ > * (We know that all the old items together can fit on one page, so > * we need not worry about any other problem than failing to fit > * the new item.) > + * > + * Guttman's algorithm actually has two factors to consider (in > + * order): 1. if one node has so many tuples already assigned to > + * it that the other needs all the rest in order to satisfy the > + * condition that neither node has fewer than m tuples, then > + * that is decisive; 2. otherwise, choose the page that shows > + * the smaller enlargement of its union area. > + * > + * I have chosen m = M/2, where M is the maximum number of > + * tuples on a page. (Actually, this is only strictly > + * true for fixed size tuples. For variable size tuples, > + * there still might have to be only one tuple on a page, > + * if it is really big. But even with variable size > + * tuples we still try to get m as close as possible to M/2.) > + * > + * The question of which page shows the smaller enlargement of > + * its union area has already been answered, and the answer > + * stored in the choose_left field of the SPLITCOST record. > */ > left_feasible = (left_avail_space >= item_1_sz && > ((left_avail_space - item_1_sz) >= newitemsz || > @@ -988,8 +1105,18 @@ > left_avail_space >= newitemsz)); > if (left_feasible && right_feasible) > { > - /* Both feasible, use Guttman's algorithm */ > - choose_left = (size_alpha - size_l < size_beta - size_r); > + /* > + * Both feasible, use Guttman's algorithm. > + * First check the m condition described above, and if > + * that doesn't apply, choose the page with the smaller > + * enlargement of its union area. > + */ > + if (v->spl_nleft > max_after_split) > + choose_left = false; > + else if (v->spl_nright > max_after_split) > + choose_left = true; > + else > + choose_left = cost_vector[n].choose_left; > } > else if (left_feasible) > choose_left = true; > @@ -1023,6 +1150,11 @@ > } > } > > + if (num_tuples_without_seeds > 0) > + { > + pfree(cost_vector); > + } > + > *left = *right = InvalidOffsetNumber; /* add ending sentinels */ > > v->spl_ldatum = datum_l; > @@ -1152,6 +1284,21 @@ > fmgr_info(size_proc, &rtstate->sizeFn); > fmgr_info(inter_proc, &rtstate->interFn); > return; > +} > + > +/* for sorting SPLITCOST records in descending order */ > +static int > +qsort_comp_splitcost(const void *a, const void *b) > +{ > + float diff = > + ((SPLITCOST *)a)->cost_differential - > + ((SPLITCOST *)b)->cost_differential; > + if (diff < 0) > + return 1; > + else if (diff > 0) > + return -1; > + else > + return 0; > } > > #ifdef RTDEBUG > > ---------------------------(end of broadcast)--------------------------- > TIP 6: Have you searched our list archives? > > http://archives.postgresql.org -- Bruce Momjian | http://candle.pha.pa.us pgman@candle.pha.pa.us | (610) 853-3000 + If your life is a hard drive, | 830 Blythe Avenue + Christ can be your backup. | Drexel Hill, Pennsylvania 19026
pgsql-patches by date: