You've already forked pgbackrest
mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2026-05-22 10:15:16 +02:00
Use bsearch() on sorted lists rather than an iterative method.
bsearch() is far more efficient than an iterative approach except in the most trivial cases. For now insert will reset the sort order to none and the list will need to be resorted before bsearch() can be used. This is necessary because item pointers are not stable after a sort, i.e. they can move around. Until lists are stable it's not a good idea to surprise the caller by mixing up their pointers on insert.
This commit is contained in:
+87
-28
@@ -21,6 +21,7 @@ struct List
|
||||
size_t itemSize;
|
||||
unsigned int listSize;
|
||||
unsigned int listSizeMax;
|
||||
SortOrder sortOrder;
|
||||
unsigned char *list;
|
||||
ListComparator *comparator;
|
||||
};
|
||||
@@ -38,7 +39,7 @@ lstNew(size_t itemSize)
|
||||
FUNCTION_TEST_PARAM(SIZE, itemSize);
|
||||
FUNCTION_TEST_END();
|
||||
|
||||
FUNCTION_TEST_RETURN(lstNewP(itemSize, .comparator = lstComparatorStr));
|
||||
FUNCTION_TEST_RETURN(lstNewP(itemSize));
|
||||
}
|
||||
|
||||
List *
|
||||
@@ -57,6 +58,7 @@ lstNewParam(size_t itemSize, ListParam param)
|
||||
this = memNew(sizeof(List));
|
||||
this->memContext = MEM_CONTEXT_NEW();
|
||||
this->itemSize = itemSize;
|
||||
this->sortOrder = param.sortOrder;
|
||||
this->comparator = param.comparator;
|
||||
}
|
||||
MEM_CONTEXT_NEW_END();
|
||||
@@ -109,7 +111,7 @@ lstClear(List *this)
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Compare Strings or structs with a String as the first member
|
||||
Compare Strings ascending or structs with a String as the first member
|
||||
***********************************************************************************************************************************/
|
||||
int
|
||||
lstComparatorStr(const void *item1, const void *item2)
|
||||
@@ -125,6 +127,17 @@ lstComparatorStr(const void *item1, const void *item2)
|
||||
FUNCTION_TEST_RETURN(strCmp(*(String **)item1, *(String **)item2));
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
General function for a descending comparator that simply switches the parameters on the main comparator (which should be asc)
|
||||
***********************************************************************************************************************************/
|
||||
static const List *comparatorDescList = NULL;
|
||||
|
||||
static int
|
||||
lstComparatorDesc(const void *item1, const void *item2)
|
||||
{
|
||||
return comparatorDescList->comparator(item2, item1);
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Get an item from the list
|
||||
***********************************************************************************************************************************/
|
||||
@@ -146,9 +159,57 @@ lstGet(const List *this, unsigned int listIdx)
|
||||
FUNCTION_TEST_RETURN(this->list + (listIdx * this->itemSize));
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Does an item exist in the list?
|
||||
***********************************************************************************************************************************/
|
||||
bool
|
||||
lstExists(const List *this, const void *item)
|
||||
{
|
||||
FUNCTION_TEST_BEGIN();
|
||||
FUNCTION_TEST_PARAM(LIST, this);
|
||||
FUNCTION_TEST_PARAM_P(VOID, item);
|
||||
FUNCTION_TEST_END();
|
||||
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(item != NULL);
|
||||
|
||||
FUNCTION_TEST_RETURN(lstFind(this, item) != NULL);
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Find an item in the list
|
||||
***********************************************************************************************************************************/
|
||||
void *
|
||||
lstFind(const List *this, const void *item)
|
||||
{
|
||||
FUNCTION_TEST_BEGIN();
|
||||
FUNCTION_TEST_PARAM(LIST, this);
|
||||
FUNCTION_TEST_PARAM_P(VOID, item);
|
||||
FUNCTION_TEST_END();
|
||||
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(item != NULL);
|
||||
|
||||
if (this->sortOrder == sortOrderAsc)
|
||||
FUNCTION_TEST_RETURN(bsearch(item, this->list, this->listSize, this->itemSize, this->comparator));
|
||||
else if (this->sortOrder == sortOrderDesc)
|
||||
{
|
||||
// Assign the list for the descending comparator to use
|
||||
comparatorDescList = this;
|
||||
|
||||
FUNCTION_TEST_RETURN(bsearch(item, this->list, this->listSize, this->itemSize, lstComparatorDesc));
|
||||
}
|
||||
|
||||
// Fall back on an iterative search
|
||||
for (unsigned int listIdx = 0; listIdx < lstSize(this); listIdx++)
|
||||
{
|
||||
if (this->comparator(item, lstGet(this, listIdx)) == 0)
|
||||
FUNCTION_TEST_RETURN(lstGet(this, listIdx));
|
||||
}
|
||||
|
||||
FUNCTION_TEST_RETURN(NULL);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
lstFindIdx(const List *this, const void *item)
|
||||
{
|
||||
@@ -160,18 +221,9 @@ lstFindIdx(const List *this, const void *item)
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(item != NULL);
|
||||
|
||||
unsigned int result = LIST_NOT_FOUND;
|
||||
void *result = lstFind(this, item);
|
||||
|
||||
for (unsigned int listIdx = 0; listIdx < lstSize(this); listIdx++)
|
||||
{
|
||||
if (this->comparator(item, lstGet(this, listIdx)) == 0)
|
||||
{
|
||||
result = listIdx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION_TEST_RETURN(result);
|
||||
FUNCTION_TEST_RETURN(result == NULL ? LIST_NOT_FOUND : lstIdx(this, result));
|
||||
}
|
||||
|
||||
void *
|
||||
@@ -186,13 +238,16 @@ lstFindDefault(const List *this, const void *item, void *itemDefault)
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(item != NULL);
|
||||
|
||||
unsigned int listIdx = lstFindIdx(this, item);
|
||||
void *result= lstFind(this, item);
|
||||
|
||||
FUNCTION_TEST_RETURN(listIdx == LIST_NOT_FOUND ? itemDefault : lstGet(this, listIdx));
|
||||
FUNCTION_TEST_RETURN(result == NULL ? itemDefault : result);
|
||||
}
|
||||
|
||||
void *
|
||||
lstFind(const List *this, const void *item)
|
||||
/***********************************************************************************************************************************
|
||||
Get the index of a list item
|
||||
***********************************************************************************************************************************/
|
||||
unsigned int
|
||||
lstIdx(const List *this, const void *item)
|
||||
{
|
||||
FUNCTION_TEST_BEGIN();
|
||||
FUNCTION_TEST_PARAM(LIST, this);
|
||||
@@ -202,7 +257,15 @@ lstFind(const List *this, const void *item)
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(item != NULL);
|
||||
|
||||
FUNCTION_TEST_RETURN(lstFindDefault(this, item, NULL));
|
||||
// Item pointers should always be aligned with the beginning of an item in the list
|
||||
ASSERT((size_t)((unsigned char * const)item - this->list) % this->itemSize == 0);
|
||||
|
||||
size_t result = (size_t)((unsigned char * const)item - this->list) / this->itemSize;
|
||||
|
||||
// Item pointers should always be in range
|
||||
ASSERT(result < this->listSize);
|
||||
|
||||
FUNCTION_TEST_RETURN((unsigned int)result);
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
@@ -248,6 +311,7 @@ lstInsert(List *this, unsigned int listIdx, const void *item)
|
||||
memmove(this->list + ((listIdx + 1) * this->itemSize), itemPtr, (lstSize(this) - listIdx) * this->itemSize);
|
||||
|
||||
// Copy item into the list
|
||||
this->sortOrder = sortOrderNone;
|
||||
memcpy(itemPtr, item, this->itemSize);
|
||||
this->listSize++;
|
||||
|
||||
@@ -332,14 +396,6 @@ lstSize(const List *this)
|
||||
/***********************************************************************************************************************************
|
||||
List sort
|
||||
***********************************************************************************************************************************/
|
||||
static List *lstSortList = NULL;
|
||||
|
||||
static int
|
||||
lstSortDescComparator(const void *item1, const void *item2)
|
||||
{
|
||||
return lstSortList->comparator(item2, item1);
|
||||
}
|
||||
|
||||
List *
|
||||
lstSort(List *this, SortOrder sortOrder)
|
||||
{
|
||||
@@ -361,9 +417,9 @@ lstSort(List *this, SortOrder sortOrder)
|
||||
case sortOrderDesc:
|
||||
{
|
||||
// Assign the list that will be sorted for the comparator function to use
|
||||
lstSortList = this;
|
||||
comparatorDescList = this;
|
||||
|
||||
qsort(this->list, this->listSize, this->itemSize, lstSortDescComparator);
|
||||
qsort(this->list, this->listSize, this->itemSize, lstComparatorDesc);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -371,6 +427,8 @@ lstSort(List *this, SortOrder sortOrder)
|
||||
break;
|
||||
}
|
||||
|
||||
this->sortOrder = sortOrder;
|
||||
|
||||
FUNCTION_TEST_RETURN(this);
|
||||
}
|
||||
|
||||
@@ -388,6 +446,7 @@ lstComparatorSet(List *this, ListComparator *comparator)
|
||||
ASSERT(this != NULL);
|
||||
|
||||
this->comparator = comparator;
|
||||
this->sortOrder = sortOrderNone;
|
||||
|
||||
FUNCTION_TEST_RETURN(this);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ List *lstNew(size_t itemSize);
|
||||
|
||||
typedef struct ListParam
|
||||
{
|
||||
SortOrder sortOrder;
|
||||
ListComparator *comparator;
|
||||
} ListParam;
|
||||
|
||||
@@ -68,9 +69,11 @@ Functions
|
||||
void *lstAdd(List *this, const void *item);
|
||||
List *lstClear(List *this);
|
||||
void *lstGet(const List *this, unsigned int listIdx);
|
||||
bool lstExists(const List *this, const void *item);
|
||||
void *lstFind(const List *this, const void *item);
|
||||
void *lstFindDefault(const List *this, const void *item, void *itemDefault);
|
||||
unsigned int lstFindIdx(const List *this, const void *item);
|
||||
unsigned int lstIdx(const List *this, const void *item);
|
||||
void *lstInsert(List *this, unsigned int listIdx, const void *item);
|
||||
bool lstRemove(List *this, const void *item);
|
||||
List *lstRemoveIdx(List *this, unsigned int listIdx);
|
||||
|
||||
@@ -281,18 +281,7 @@ strLstExists(const StringList *this, const String *string)
|
||||
|
||||
ASSERT(this != NULL);
|
||||
|
||||
bool result = false;
|
||||
|
||||
for (unsigned int listIdx = 0; listIdx < strLstSize(this); listIdx++)
|
||||
{
|
||||
if (strEq(strLstGet(this, listIdx), string))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION_TEST_RETURN(result);
|
||||
FUNCTION_TEST_RETURN(lstExists((List *)this, &string));
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -305,18 +294,9 @@ strLstExistsZ(const StringList *this, const char *cstring)
|
||||
|
||||
ASSERT(this != NULL);
|
||||
|
||||
bool result = false;
|
||||
const String *string = cstring == NULL ? NULL : STR(cstring);
|
||||
|
||||
for (unsigned int listIdx = 0; listIdx < strLstSize(this); listIdx++)
|
||||
{
|
||||
if (strEqZ(strLstGet(this, listIdx), cstring))
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION_TEST_RETURN(result);
|
||||
FUNCTION_TEST_RETURN(lstExists((List *)this, &string));
|
||||
}
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ unit:
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------------
|
||||
- name: type-list
|
||||
total: 3
|
||||
total: 4
|
||||
|
||||
coverage:
|
||||
common/type/list: full
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/***********************************************************************************************************************************
|
||||
Test Lists
|
||||
***********************************************************************************************************************************/
|
||||
#include "common/time.h"
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Test sort comparator
|
||||
@@ -59,7 +60,10 @@ testRun(void)
|
||||
|
||||
String *string3 = strNew("string3");
|
||||
TEST_RESULT_PTR(lstFindDefault(list, &string3, (void *)1), (void *)1, " find string3 returns default");
|
||||
TEST_RESULT_BOOL(lstExists(list, &string3), false, " string3 does not exist");
|
||||
TEST_RESULT_STR(strPtr(*(String **)lstFind(list, &string2)), "string2", " find string2");
|
||||
TEST_RESULT_STR(strPtr(*(String **)lstFindDefault(list, &string2, NULL)), "string2", " find string2 no default");
|
||||
TEST_RESULT_BOOL(lstExists(list, &string2), true, " string2 exists");
|
||||
|
||||
TEST_RESULT_BOOL(lstRemove(list, &string2), true, " remove string2");
|
||||
TEST_RESULT_BOOL(lstRemove(list, &string2), false, " unable to remove string2");
|
||||
@@ -155,5 +159,38 @@ testRun(void)
|
||||
TEST_RESULT_INT(*((int *)lstGet(list, 3)), 2, "sort value 3");
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************
|
||||
if (testBegin("performance"))
|
||||
{
|
||||
int testMax = 2000;
|
||||
List *list = lstNewP(sizeof(int), .comparator = testComparator);
|
||||
|
||||
// Generate a large list of values (use int instead of string so there fewer allocations)
|
||||
for (int listIdx = 0; listIdx < testMax; listIdx++)
|
||||
lstAdd(list, &listIdx);
|
||||
|
||||
TEST_RESULT_UINT(lstSize(list), testMax, "check list total");
|
||||
|
||||
// Search for all values with an ascending sort
|
||||
lstSort(list, sortOrderAsc);
|
||||
|
||||
TimeMSec timeBegin = timeMSec();
|
||||
|
||||
for (int listIdx = 0; listIdx < testMax; listIdx++)
|
||||
CHECK(*(int *)lstFind(list, &listIdx) == listIdx);
|
||||
|
||||
TEST_LOG_FMT("asc search completed in %ums", (unsigned int)(timeMSec() - timeBegin));
|
||||
|
||||
// Search for all values with an descending sort
|
||||
lstSort(list, sortOrderDesc);
|
||||
|
||||
timeBegin = timeMSec();
|
||||
|
||||
for (int listIdx = 0; listIdx < testMax; listIdx++)
|
||||
CHECK(*(int *)lstFind(list, &listIdx) == listIdx);
|
||||
|
||||
TEST_LOG_FMT("desc search completed in %ums", (unsigned int)(timeMSec() - timeBegin));
|
||||
}
|
||||
|
||||
FUNCTION_HARNESS_RESULT_VOID();
|
||||
}
|
||||
|
||||
@@ -312,6 +312,7 @@ testRun(void)
|
||||
TEST_RESULT_VOID(strLstAddIfMissing(list, STRDEF("item1")), "add item 1");
|
||||
TEST_RESULT_UINT(strLstSize(list), 1, "check size");
|
||||
TEST_RESULT_BOOL(strLstExistsZ(list, "item1"), true, "check exists");
|
||||
TEST_RESULT_BOOL(strLstExistsZ(list, NULL), false, "check null exists");
|
||||
TEST_RESULT_VOID(strLstAddIfMissing(list, STRDEF("item1")), "add item 1 again");
|
||||
TEST_RESULT_UINT(strLstSize(list), 1, "check size");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user