Patch: ResourceOwner optimization for tables with many partitions
Hello all,
Current implementation of ResourceOwner uses arrays to store resources
like TupleDescs, Snapshots, etc. When we want to release one of these
resources ResourceOwner finds it with linear scan. Granted, resource
array are usually small so its not a problem most of the time. But it
appears to be a bottleneck when we are working with tables which have a
lot of partitions.
To reproduce this issue:
1. run `./gen.pl 10000 | psql my_database postgres`
2. run `pgbench -j 8 -c 8 -f q.sql -T 100 my_database`
3. in second terminal run `sudo perf top -u postgres`
Both gen.pl and q.sql are attached to this message.
You will see that postgres spends a lot of time in ResourceOwnerForget*
procedures:
32.80% postgres [.] list_nth
20.29% postgres [.] ResourceOwnerForgetRelationRef
12.87% postgres [.] find_all_inheritors
7.90% postgres [.] get_tabstat_entry
6.68% postgres [.] ResourceOwnerForgetTupleDesc
1.17% postgres [.] hash_search_with_hash_value
... < 1% ...
I would like to suggest a patch (see attachment) witch fixes this
bottleneck. Also I discovered that there is a lot of code duplication in
ResourceOwner. Patch fixes this too. The idea is quite simple. I just
replaced arrays with something that could be considered hash tables,
but with some specific optimizations.
After applying this patch we can see that bottleneck is gone:
42.89% postgres [.] list_nth
18.30% postgres [.] find_all_inheritors
10.97% postgres [.] get_tabstat_entry
1.82% postgres [.] hash_search_with_hash_value
1.21% postgres [.] SearchCatCache
... < 1% ...
For tables with thousands partitions we gain in average 32.5% more TPS.
As far as I can see in the same time patch doesn't make anything worse.
`make check` passes with asserts enabled and disabled. There is no
performance degradation according to both standard pgbench benchmark
and benchmark described above for tables with 10 and 100 partitions.
Best regards,
Aleksander
Attachments:
resource-owner-optimization.patchtext/x-patchDownload
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 0e7acbf..6abbfa5 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -47,62 +47,296 @@
#define MAX_RESOWNER_LOCKS 15
/*
- * ResourceOwner objects look like this
+ * This number is used as initial size of resource arrays (like buffers,
+ * catrefs, etc) in ResourceOwnerData structure. If given number of items is
+ * not enough, we double array size and reallocate memory.
+ *
+ * Should be power of two since we use (arrsize -1) as mask for hash value.
+ *
*/
-typedef struct ResourceOwnerData
+#define RESARRAY_INIT_SIZE 16
+
+/*
+ * How many items could be stored in a resource array of given capacity. If
+ * this number is reached we need to resize an array to prevent hash collisions.
+ *
+ * This computation actually costs only two additions and one binary shift.
+ */
+#define RESARRAY_MAX_ITEMS(capacity) ((capacity)*3/4)
+
+/*
+ * Common structure for storing different type of resources.
+ */
+typedef struct ResourceArray
+{
+ uint8* itemsarr; /* buffer for storing values */
+ uint32 itemsizelg: 2; /* sizeof one item log 2 */
+ uint32 capacity: 30; /* capacity of array */
+ uint32 nitems; /* how many items is stored in items array */
+ uint32 maxitems; /* precalculated RESARRAY_MAX_ITEMS(capacity) */
+} ResourceArray;
+
+/*
+ * Such type of callback function is called when resource stored in
+ * ResourceArray is released using ResourceArrayFree. Callback should
+ * _actually_ release a resource so nitems value will be decremented.
+ */
+typedef void (*ResourceArrayRemoveCallback)(const uint8* ref, bool isCommit);
+
+/* Used as argument to memcmp to determine if ResourceArray[i] is free. */
+static const uint8 RESOURCE_ARRAY_ZERO_ELEMENT[sizeof(void*)] = {0};
+
+/*
+ * Calculate hash_any of given data. For uint32 values use faster hash_uint32.
+ */
+static Datum
+ResourceArrayHash(const uint8* data, int size)
+{
+ uint32 tmp;
+
+ Assert(size == sizeof(uint32) || size == sizeof(void*));
+
+ if(size == sizeof(uint32))
+ {
+ tmp = *((const uint32*)data);
+ return hash_uint32(tmp);
+ }
+ else
+ {
+ return hash_any(data, size);
+ }
+}
+
+/*
+ * Initialize ResourceArray
+ */
+static void
+ResourceArrayInit(ResourceArray *resarr, uint32 itemsize) {
+ Assert(itemsize == sizeof(int) || itemsize == sizeof(void*));
+ Assert(resarr->itemsarr == NULL);
+ Assert(resarr->capacity == 0);
+ Assert(resarr->nitems == 0);
+ Assert(resarr->maxitems == 0);
+
+ resarr->itemsizelg = 0;
+ while(itemsize > 1)
+ {
+ resarr->itemsizelg++;
+ itemsize >>= 1;
+ }
+
+ Assert(resarr->itemsizelg == 2 || resarr->itemsizelg == 3);
+}
+
+/*
+ * Add a resource to ResourceArray
+ *
+ * Caller must have previously done ResourceArrayEnlarge()
+ */
+static void
+ResourceArrayAdd(ResourceArray *resarr, const uint8 *dataref)
+{
+ uint8 *itemptr;
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(memcmp(dataref, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0);
+ Assert(resarr->maxitems > resarr->nitems);
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+
+ /*
+ * Hashing is quite expensive, so we use it only for large arrays. For
+ * small arrays we just use a linear scan.
+ */
+ if(resarr->capacity == RESARRAY_INIT_SIZE)
+ idx = resarr->nitems;
+ else
+ idx = ResourceArrayHash(dataref, itemsize);
+ idx &= mask;
+
+ while(true)
+ {
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if(memcmp(itemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) == 0)
+ break;
+ idx = (idx + 1) & mask;
+ }
+
+ memcpy(itemptr, dataref, itemsize);
+ resarr->nitems++;
+}
+
+/*
+ * Remove a resource from ResourceArray
+ *
+ * Returns true on success, false if resource was not found
+ */
+static bool
+ResourceArrayRemove(ResourceArray *resarr, const uint8 *dataref)
+{
+ uint8 *itemptr;
+ Datum idx;
+ uint32 i;
+ Datum mask = resarr->capacity - 1;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(memcmp(dataref, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0);
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+
+ /*
+ * Hashing is quite expensive, so we use it only for large arrays. For
+ * small arrays we use a linear scan.
+ */
+ if(resarr->capacity == RESARRAY_INIT_SIZE)
+ idx = 0;
+ else
+ idx = ResourceArrayHash(dataref, itemsize);
+ idx &= mask;
+
+ for(i = 0; i < resarr->capacity; i++)
+ {
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if(memcmp(itemptr, dataref, itemsize) == 0)
+ {
+ memset(itemptr, 0, itemsize);
+ resarr->nitems--;
+ return true;
+ }
+ idx = (idx + 1) & mask;
+ }
+
+ return false;
+}
+
+/*
+ * Make sure there is a room for at least one more resource in an array.
+ *
+ * This is separate from actually inserting a resource because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+static void
+ResourceArrayEnlarge(ResourceArray* resarr)
+{
+ uint32 oldcap;
+ uint8* olditemsarr;
+ uint8* olditemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(resarr->itemsizelg != 0);
+
+ if(resarr->nitems < resarr->maxitems)
+ return; /* nothing to do */
+
+ olditemsarr = resarr->itemsarr;
+ oldcap = resarr->capacity;
+
+ resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
+ resarr->itemsarr = (uint8*)
+ MemoryContextAllocZero(TopMemoryContext,
+ resarr->capacity * itemsize);
+ resarr->maxitems = RESARRAY_MAX_ITEMS(resarr->capacity);
+ resarr->nitems = 0;
+
+ if(olditemsarr != NULL)
+ {
+ while(oldcap > 0)
+ {
+ oldcap--;
+ /*
+ * Read next line as:
+ *
+ * olditemptr = &(olditemsarr[oldcap]).
+ *
+ * We use binary shift since compiler don't know that itemsize
+ * is always power of two. It would use multiplication instead of
+ * efficient binary shift in code `oldcap * itemsize`.
+ */
+ olditemptr = olditemsarr + (oldcap << resarr->itemsizelg);
+ if(memcmp(olditemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0)
+ ResourceArrayAdd(resarr, olditemptr);
+ }
+ pfree(olditemsarr);
+ }
+}
+
+/*
+ * Remove all resources in array and call a callback function for each release.
+ */
+static void
+ResourceArrayRemoveAll(ResourceArray *resarr,
+ ResourceArrayRemoveCallback releasecb,
+ bool isCommit)
+{
+ uint32 idx = 0;
+ uint8* itemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ while(resarr->nitems > 0)
+ {
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx]).
+ *
+ * We use binary shift since compiler don't know that itemsize
+ * is always power of two. It would use multiplication instead of
+ * efficient binary shift in code `oldcap * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if(memcmp(itemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) == 0)
+ {
+ idx++;
+ continue;
+ }
+ releasecb(itemptr, isCommit);
+ }
+}
+
+/*
+ * Return ResourceArray to initial state
+ */
+static void
+ResourceArrayFree(ResourceArray *resarr)
{
- ResourceOwner parent; /* NULL if no parent (toplevel owner) */
- ResourceOwner firstchild; /* head of linked list of children */
- ResourceOwner nextchild; /* next child of same parent */
- const char *name; /* name (just for debugging) */
+ Assert(resarr->nitems == 0);
+
+ resarr->capacity = 0;
+ resarr->maxitems = 0;
- /* We have built-in support for remembering owned buffers */
- int nbuffers; /* number of owned buffer pins */
- Buffer *buffers; /* dynamically allocated array */
- int maxbuffers; /* currently allocated array size */
+ if(!resarr->itemsarr)
+ return;
+
+ pfree(resarr->itemsarr);
+ resarr->itemsarr = NULL;
+}
+
+/* ResourceOwner objects look like this */
+typedef struct ResourceOwnerData
+{
+ ResourceOwner parent; /* NULL if no parent (toplevel owner) */
+ ResourceOwner firstchild; /* head of linked list of children */
+ ResourceOwner nextchild; /* next child of same parent */
+ const char *name; /* name (just for debugging) */
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
- int nlocks; /* number of owned locks */
+ int nlocks; /* number of owned locks */
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
- /* We have built-in support for remembering catcache references */
- int ncatrefs; /* number of owned catcache pins */
- HeapTuple *catrefs; /* dynamically allocated array */
- int maxcatrefs; /* currently allocated array size */
-
- int ncatlistrefs; /* number of owned catcache-list pins */
- CatCList **catlistrefs; /* dynamically allocated array */
- int maxcatlistrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering relcache references */
- int nrelrefs; /* number of owned relcache pins */
- Relation *relrefs; /* dynamically allocated array */
- int maxrelrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering plancache references */
- int nplanrefs; /* number of owned plancache pins */
- CachedPlan **planrefs; /* dynamically allocated array */
- int maxplanrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering tupdesc references */
- int ntupdescs; /* number of owned tupdesc references */
- TupleDesc *tupdescs; /* dynamically allocated array */
- int maxtupdescs; /* currently allocated array size */
-
- /* We have built-in support for remembering snapshot references */
- int nsnapshots; /* number of owned snapshot references */
- Snapshot *snapshots; /* dynamically allocated array */
- int maxsnapshots; /* currently allocated array size */
-
- /* We have built-in support for remembering open temporary files */
- int nfiles; /* number of owned temporary files */
- File *files; /* dynamically allocated array */
- int maxfiles; /* currently allocated array size */
-
- /* We have built-in support for remembering dynamic shmem segments */
- int ndsms; /* number of owned shmem segments */
- dsm_segment **dsms; /* dynamically allocated array */
- int maxdsms; /* currently allocated array size */
+ /* We have built-in support for remembering: */
+
+ ResourceArray catrefarr; /* `HeapTuple`s */
+ ResourceArray catlistrefarr; /* `ResourceOwner`s */
+ ResourceArray relrefarr; /* `Relation`s */
+ ResourceArray planrefarr; /* `CachedPlan*`s */
+ ResourceArray tupdescarr; /* `TupleDesc`s */
+ ResourceArray snapshotarr; /* `Snapshot`s */
+ ResourceArray dsmarr; /* `dsm_segment*`s */
+ ResourceArray bufferarr; /* `Buffer`s */
+ ResourceArray filearr; /* `File`s */
+
} ResourceOwnerData;
@@ -168,6 +402,16 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
+ ResourceArrayInit(&(owner->catrefarr), sizeof(HeapTuple));
+ ResourceArrayInit(&(owner->catlistrefarr), sizeof(ResourceOwner));
+ ResourceArrayInit(&(owner->relrefarr), sizeof(Relation));
+ ResourceArrayInit(&(owner->planrefarr), sizeof(CachedPlan*));
+ ResourceArrayInit(&(owner->tupdescarr), sizeof(TupleDesc));
+ ResourceArrayInit(&(owner->snapshotarr), sizeof(Snapshot));
+ ResourceArrayInit(&(owner->dsmarr), sizeof(dsm_segment*));
+ ResourceArrayInit(&(owner->bufferarr), sizeof(Buffer));
+ ResourceArrayInit(&(owner->filearr), sizeof(File));
+
return owner;
}
@@ -221,6 +465,87 @@ ResourceOwnerRelease(ResourceOwner owner,
}
static void
+ReleaseCachedPlanCallback(const uint8* dataref, bool isCommit)
+{
+ CachedPlan* res = *((CachedPlan**)dataref);
+ if (isCommit)
+ PrintPlanCacheLeakWarning(res);
+ ReleaseCachedPlan(res, true);
+}
+
+static void
+ReleaseDSMCallback(const uint8* dataref, bool isCommit)
+{
+ dsm_segment* res = *((dsm_segment**)dataref);
+ if (isCommit)
+ PrintDSMLeakWarning(res);
+ dsm_detach(res);
+}
+
+static void
+ReleaseTupleDescCallback(const uint8* dataref, bool isCommit)
+{
+ TupleDesc res = *((TupleDesc*)dataref);
+ if(isCommit)
+ PrintTupleDescLeakWarning(res);
+ DecrTupleDescRefCount(res);
+}
+
+static void
+ReleaseSnapshotCallback(const uint8* dataref, bool isCommit)
+{
+ Snapshot res = *((Snapshot*)dataref);
+ if (isCommit)
+ PrintSnapshotLeakWarning(res);
+ UnregisterSnapshot(res);
+}
+
+static void
+ReleaseRelationCallback(const uint8* dataref, bool isCommit)
+{
+ Relation res = *((Relation*)dataref);
+ if (isCommit)
+ PrintRelCacheLeakWarning(res);
+ RelationClose(res);
+}
+
+static void
+ReleaseCatCacheListCallback(const uint8* dataref, bool isCommit)
+{
+ CatCList* res = *((CatCList**)dataref);
+ if (isCommit)
+ PrintCatCacheListLeakWarning(res);
+ ReleaseCatCacheList(res);
+}
+
+static void
+ReleaseCatCacheCallback(const uint8* dataref, bool isCommit)
+{
+ HeapTuple res = *((HeapTuple*)dataref);
+ if (isCommit)
+ PrintCatCacheLeakWarning(res);
+ ReleaseCatCache(res);
+}
+
+static void
+ReleaseBufferCallback(const uint8* dataref, bool isCommit)
+{
+ Buffer res = *((Buffer*)dataref);
+ if (isCommit)
+ PrintBufferLeakWarning(res);
+ ReleaseBuffer(res);
+}
+
+static void
+ReleaseFileCallback(const uint8* dataref, bool isCommit)
+{
+ File res = *((File*)dataref);
+ if (isCommit)
+ PrintFileLeakWarning(res);
+ FileClose(res);
+}
+
+static void
ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceReleasePhase phase,
bool isCommit,
@@ -252,46 +577,17 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* During a commit, there shouldn't be any remaining pins --- that
* would indicate failure to clean up the executor correctly --- so
* issue warnings. In the abort case, just clean up quietly.
- *
- * We are careful to do the releasing back-to-front, so as to avoid
- * O(N^2) behavior in ResourceOwnerForgetBuffer().
*/
- while (owner->nbuffers > 0)
- {
- if (isCommit)
- PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
- ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->bufferarr),
+ ReleaseBufferCallback, isCommit);
- /*
- * Release relcache references. Note that RelationClose will remove
- * the relref entry from my list, so I just have to iterate till there
- * are none.
- *
- * As with buffer pins, warn if any are left at commit time, and
- * release back-to-front for speed.
- */
- while (owner->nrelrefs > 0)
- {
- if (isCommit)
- PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]);
- RelationClose(owner->relrefs[owner->nrelrefs - 1]);
- }
+ /* Ditto for relcache references. */
+ ResourceArrayRemoveAll(&(owner->relrefarr),
+ ReleaseRelationCallback, isCommit);
- /*
- * Release dynamic shared memory segments. Note that dsm_detach()
- * will remove the segment from my list, so I just have to iterate
- * until there are none.
- *
- * As in the preceding cases, warn if there are leftover at commit
- * time.
- */
- while (owner->ndsms > 0)
- {
- if (isCommit)
- PrintDSMLeakWarning(owner->dsms[owner->ndsms - 1]);
- dsm_detach(owner->dsms[owner->ndsms - 1]);
- }
+ /* Ditto for dynamic shared memory segments */
+ ResourceArrayRemoveAll(&(owner->dsmarr),
+ ReleaseDSMCallback, isCommit);
}
else if (phase == RESOURCE_RELEASE_LOCKS)
{
@@ -351,48 +647,28 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
- while (owner->ncatrefs > 0)
- {
- if (isCommit)
- PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]);
- ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->catrefarr),
+ ReleaseCatCacheCallback, isCommit);
+
/* Ditto for catcache lists */
- while (owner->ncatlistrefs > 0)
- {
- if (isCommit)
- PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
- ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->catlistrefarr),
+ ReleaseCatCacheListCallback, isCommit);
+
/* Ditto for plancache references */
- while (owner->nplanrefs > 0)
- {
- if (isCommit)
- PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
- ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
- }
+ ResourceArrayRemoveAll(&(owner->planrefarr),
+ ReleaseCachedPlanCallback, isCommit);
+
/* Ditto for tupdesc references */
- while (owner->ntupdescs > 0)
- {
- if (isCommit)
- PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
- DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->tupdescarr),
+ ReleaseTupleDescCallback, isCommit);
+
/* Ditto for snapshot references */
- while (owner->nsnapshots > 0)
- {
- if (isCommit)
- PrintSnapshotLeakWarning(owner->snapshots[owner->nsnapshots - 1]);
- UnregisterSnapshot(owner->snapshots[owner->nsnapshots - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->snapshotarr),
+ ReleaseSnapshotCallback, isCommit);
/* Ditto for temporary files */
- while (owner->nfiles > 0)
- {
- if (isCommit)
- PrintFileLeakWarning(owner->files[owner->nfiles - 1]);
- FileClose(owner->files[owner->nfiles - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->filearr),
+ ReleaseFileCallback, isCommit);
/* Clean up index scans too */
ReleaseResources_hash();
@@ -418,16 +694,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner != CurrentResourceOwner);
/* And it better not own any resources, either */
- Assert(owner->nbuffers == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
- Assert(owner->ncatrefs == 0);
- Assert(owner->ncatlistrefs == 0);
- Assert(owner->nrelrefs == 0);
- Assert(owner->ndsms == 0);
- Assert(owner->nplanrefs == 0);
- Assert(owner->ntupdescs == 0);
- Assert(owner->nsnapshots == 0);
- Assert(owner->nfiles == 0);
/*
* Delete children. The recursive call will delink the child from me, so
@@ -444,25 +711,15 @@ ResourceOwnerDelete(ResourceOwner owner)
ResourceOwnerNewParent(owner, NULL);
/* And free the object. */
- if (owner->buffers)
- pfree(owner->buffers);
- if (owner->catrefs)
- pfree(owner->catrefs);
- if (owner->catlistrefs)
- pfree(owner->catlistrefs);
- if (owner->relrefs)
- pfree(owner->relrefs);
- if (owner->planrefs)
- pfree(owner->planrefs);
- if (owner->tupdescs)
- pfree(owner->tupdescs);
- if (owner->snapshots)
- pfree(owner->snapshots);
- if (owner->files)
- pfree(owner->files);
- if (owner->dsms)
- pfree(owner->dsms);
-
+ ResourceArrayFree(&(owner->catrefarr));
+ ResourceArrayFree(&(owner->catlistrefarr));
+ ResourceArrayFree(&(owner->relrefarr));
+ ResourceArrayFree(&(owner->planrefarr));
+ ResourceArrayFree(&(owner->tupdescarr));
+ ResourceArrayFree(&(owner->snapshotarr));
+ ResourceArrayFree(&(owner->dsmarr));
+ ResourceArrayFree(&(owner->bufferarr));
+ ResourceArrayFree(&(owner->filearr));
pfree(owner);
}
@@ -575,26 +832,9 @@ UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
void
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
{
- int newmax;
-
- if (owner == NULL ||
- owner->nbuffers < owner->maxbuffers)
- return; /* nothing to do */
-
- if (owner->buffers == NULL)
- {
- newmax = 16;
- owner->buffers = (Buffer *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
- else
- {
- newmax = owner->maxbuffers * 2;
- owner->buffers = (Buffer *)
- repalloc(owner->buffers, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayEnlarge(&(owner->bufferarr));
}
/*
@@ -608,12 +848,9 @@ ResourceOwnerEnlargeBuffers(ResourceOwner owner)
void
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Assert(owner->nbuffers < owner->maxbuffers);
- owner->buffers[owner->nbuffers] = buffer;
- owner->nbuffers++;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayAdd(&(owner->bufferarr), (const uint8*)&buffer);
}
/*
@@ -625,33 +862,15 @@ ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Buffer *buffers = owner->buffers;
- int nb1 = owner->nbuffers - 1;
- int i;
+ bool res;
- /*
- * Scan back-to-front because it's more likely we are releasing a
- * recently pinned buffer. This isn't always the case of course, but
- * it's the way to bet.
- */
- for (i = nb1; i >= 0; i--)
- {
- if (buffers[i] == buffer)
- {
- while (i < nb1)
- {
- buffers[i] = buffers[i + 1];
- i++;
- }
- owner->nbuffers = nb1;
- return;
- }
- }
+ if (owner == NULL)
+ return;
+
+ res = ResourceArrayRemove(&(owner->bufferarr), (const uint8*)&buffer);
+ if(!res)
elog(ERROR, "buffer %d is not owned by resource owner %s",
buffer, owner->name);
- }
}
/*
@@ -667,6 +886,8 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
{
+ Assert(locallock != NULL);
+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have already overflowed */
@@ -714,25 +935,7 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
void
ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatrefs < owner->maxcatrefs)
- return; /* nothing to do */
-
- if (owner->catrefs == NULL)
- {
- newmax = 16;
- owner->catrefs = (HeapTuple *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatrefs * 2;
- owner->catrefs = (HeapTuple *)
- repalloc(owner->catrefs, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catrefarr));
}
/*
@@ -743,9 +946,7 @@ ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- Assert(owner->ncatrefs < owner->maxcatrefs);
- owner->catrefs[owner->ncatrefs] = tuple;
- owner->ncatrefs++;
+ ResourceArrayAdd(&(owner->catrefarr), (const uint8*)&tuple);
}
/*
@@ -754,25 +955,11 @@ ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- HeapTuple *catrefs = owner->catrefs;
- int nc1 = owner->ncatrefs - 1;
- int i;
-
- for (i = nc1; i >= 0; i--)
- {
- if (catrefs[i] == tuple)
- {
- while (i < nc1)
- {
- catrefs[i] = catrefs[i + 1];
- i++;
- }
- owner->ncatrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache reference %p is not owned by resource owner %s",
- tuple, owner->name);
+ bool res = ResourceArrayRemove(&(owner->catrefarr),
+ (const uint8*)&tuple);
+ if(!res)
+ elog(ERROR, "catcache reference %p is not owned by resource owner %s",
+ tuple, owner->name);
}
/*
@@ -785,25 +972,7 @@ ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatlistrefs < owner->maxcatlistrefs)
- return; /* nothing to do */
-
- if (owner->catlistrefs == NULL)
- {
- newmax = 16;
- owner->catlistrefs = (CatCList **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatlistrefs * 2;
- owner->catlistrefs = (CatCList **)
- repalloc(owner->catlistrefs, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catlistrefarr));
}
/*
@@ -814,9 +983,7 @@ ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- Assert(owner->ncatlistrefs < owner->maxcatlistrefs);
- owner->catlistrefs[owner->ncatlistrefs] = list;
- owner->ncatlistrefs++;
+ ResourceArrayAdd(&(owner->catlistrefarr), (const uint8*)&list);
}
/*
@@ -825,25 +992,11 @@ ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- CatCList **catlistrefs = owner->catlistrefs;
- int nc1 = owner->ncatlistrefs - 1;
- int i;
-
- for (i = nc1; i >= 0; i--)
- {
- if (catlistrefs[i] == list)
- {
- while (i < nc1)
- {
- catlistrefs[i] = catlistrefs[i + 1];
- i++;
- }
- owner->ncatlistrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
- list, owner->name);
+ bool res = ResourceArrayRemove(&(owner->catlistrefarr),
+ (const uint8*)&list);
+ if(!res)
+ elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
+ list, owner->name);
}
/*
@@ -856,25 +1009,7 @@ ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nrelrefs < owner->maxrelrefs)
- return; /* nothing to do */
-
- if (owner->relrefs == NULL)
- {
- newmax = 16;
- owner->relrefs = (Relation *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
- else
- {
- newmax = owner->maxrelrefs * 2;
- owner->relrefs = (Relation *)
- repalloc(owner->relrefs, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->relrefarr));
}
/*
@@ -885,9 +1020,7 @@ ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
- Assert(owner->nrelrefs < owner->maxrelrefs);
- owner->relrefs[owner->nrelrefs] = rel;
- owner->nrelrefs++;
+ ResourceArrayAdd(&(owner->relrefarr), (const uint8*)&rel);
}
/*
@@ -896,25 +1029,11 @@ ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
- Relation *relrefs = owner->relrefs;
- int nr1 = owner->nrelrefs - 1;
- int i;
-
- for (i = nr1; i >= 0; i--)
- {
- if (relrefs[i] == rel)
- {
- while (i < nr1)
- {
- relrefs[i] = relrefs[i + 1];
- i++;
- }
- owner->nrelrefs = nr1;
- return;
- }
- }
- elog(ERROR, "relcache reference %s is not owned by resource owner %s",
- RelationGetRelationName(rel), owner->name);
+ bool res = ResourceArrayRemove(&(owner->relrefarr),
+ (const uint8*)&rel);
+ if(!res)
+ elog(ERROR, "relcache reference %s is not owned by resource owner %s",
+ RelationGetRelationName(rel), owner->name);
}
/*
@@ -937,25 +1056,7 @@ PrintRelCacheLeakWarning(Relation rel)
void
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nplanrefs < owner->maxplanrefs)
- return; /* nothing to do */
-
- if (owner->planrefs == NULL)
- {
- newmax = 16;
- owner->planrefs = (CachedPlan **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
- else
- {
- newmax = owner->maxplanrefs * 2;
- owner->planrefs = (CachedPlan **)
- repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->planrefarr));
}
/*
@@ -966,9 +1067,7 @@ ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- Assert(owner->nplanrefs < owner->maxplanrefs);
- owner->planrefs[owner->nplanrefs] = plan;
- owner->nplanrefs++;
+ ResourceArrayAdd(&(owner->planrefarr), (const uint8*)&plan);
}
/*
@@ -977,25 +1076,11 @@ ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
void
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- CachedPlan **planrefs = owner->planrefs;
- int np1 = owner->nplanrefs - 1;
- int i;
-
- for (i = np1; i >= 0; i--)
- {
- if (planrefs[i] == plan)
- {
- while (i < np1)
- {
- planrefs[i] = planrefs[i + 1];
- i++;
- }
- owner->nplanrefs = np1;
- return;
- }
- }
- elog(ERROR, "plancache reference %p is not owned by resource owner %s",
- plan, owner->name);
+ bool res = ResourceArrayRemove(&(owner->planrefarr),
+ (const uint8*)&plan);
+ if(!res)
+ elog(ERROR, "plancache reference %p is not owned by resource owner %s",
+ plan, owner->name);
}
/*
@@ -1017,25 +1102,7 @@ PrintPlanCacheLeakWarning(CachedPlan *plan)
void
ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ntupdescs < owner->maxtupdescs)
- return; /* nothing to do */
-
- if (owner->tupdescs == NULL)
- {
- newmax = 16;
- owner->tupdescs = (TupleDesc *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
- else
- {
- newmax = owner->maxtupdescs * 2;
- owner->tupdescs = (TupleDesc *)
- repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->tupdescarr));
}
/*
@@ -1046,9 +1113,7 @@ ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
void
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- Assert(owner->ntupdescs < owner->maxtupdescs);
- owner->tupdescs[owner->ntupdescs] = tupdesc;
- owner->ntupdescs++;
+ ResourceArrayAdd(&(owner->tupdescarr), (const uint8*)&tupdesc);
}
/*
@@ -1057,25 +1122,11 @@ ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
void
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- TupleDesc *tupdescs = owner->tupdescs;
- int nt1 = owner->ntupdescs - 1;
- int i;
-
- for (i = nt1; i >= 0; i--)
- {
- if (tupdescs[i] == tupdesc)
- {
- while (i < nt1)
- {
- tupdescs[i] = tupdescs[i + 1];
- i++;
- }
- owner->ntupdescs = nt1;
- return;
- }
- }
- elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
- tupdesc, owner->name);
+ bool res = ResourceArrayRemove(&(owner->tupdescarr),
+ (const uint8*)&tupdesc);
+ if(!res)
+ elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
+ tupdesc, owner->name);
}
/*
@@ -1099,25 +1150,7 @@ PrintTupleDescLeakWarning(TupleDesc tupdesc)
void
ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nsnapshots < owner->maxsnapshots)
- return; /* nothing to do */
-
- if (owner->snapshots == NULL)
- {
- newmax = 16;
- owner->snapshots = (Snapshot *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
- else
- {
- newmax = owner->maxsnapshots * 2;
- owner->snapshots = (Snapshot *)
- repalloc(owner->snapshots, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
+ ResourceArrayEnlarge(&(owner->snapshotarr));
}
/*
@@ -1128,9 +1161,7 @@ ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
void
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Assert(owner->nsnapshots < owner->maxsnapshots);
- owner->snapshots[owner->nsnapshots] = snapshot;
- owner->nsnapshots++;
+ ResourceArrayAdd(&(owner->snapshotarr), (const uint8*)&snapshot);
}
/*
@@ -1139,25 +1170,11 @@ ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
void
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Snapshot *snapshots = owner->snapshots;
- int ns1 = owner->nsnapshots - 1;
- int i;
-
- for (i = ns1; i >= 0; i--)
- {
- if (snapshots[i] == snapshot)
- {
- while (i < ns1)
- {
- snapshots[i] = snapshots[i + 1];
- i++;
- }
- owner->nsnapshots = ns1;
- return;
- }
- }
- elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
- snapshot, owner->name);
+ bool res = ResourceArrayRemove(&(owner->snapshotarr),
+ (const uint8*)&snapshot);
+ if(!res)
+ elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
+ snapshot, owner->name);
}
/*
@@ -1182,25 +1199,7 @@ PrintSnapshotLeakWarning(Snapshot snapshot)
void
ResourceOwnerEnlargeFiles(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nfiles < owner->maxfiles)
- return; /* nothing to do */
-
- if (owner->files == NULL)
- {
- newmax = 16;
- owner->files = (File *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
- else
- {
- newmax = owner->maxfiles * 2;
- owner->files = (File *)
- repalloc(owner->files, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
+ ResourceArrayEnlarge(&(owner->filearr));
}
/*
@@ -1211,9 +1210,7 @@ ResourceOwnerEnlargeFiles(ResourceOwner owner)
void
ResourceOwnerRememberFile(ResourceOwner owner, File file)
{
- Assert(owner->nfiles < owner->maxfiles);
- owner->files[owner->nfiles] = file;
- owner->nfiles++;
+ ResourceArrayAdd(&(owner->filearr), (const uint8*)&file);
}
/*
@@ -1222,25 +1219,10 @@ ResourceOwnerRememberFile(ResourceOwner owner, File file)
void
ResourceOwnerForgetFile(ResourceOwner owner, File file)
{
- File *files = owner->files;
- int ns1 = owner->nfiles - 1;
- int i;
-
- for (i = ns1; i >= 0; i--)
- {
- if (files[i] == file)
- {
- while (i < ns1)
- {
- files[i] = files[i + 1];
- i++;
- }
- owner->nfiles = ns1;
- return;
- }
- }
- elog(ERROR, "temporery file %d is not owned by resource owner %s",
- file, owner->name);
+ bool res = ResourceArrayRemove(&(owner->filearr), (const uint8*)&file);
+ if(!res)
+ elog(ERROR, "temporery file %d is not owned by resource owner %s",
+ file, owner->name);
}
@@ -1265,26 +1247,7 @@ PrintFileLeakWarning(File file)
void
ResourceOwnerEnlargeDSMs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ndsms < owner->maxdsms)
- return; /* nothing to do */
-
- if (owner->dsms == NULL)
- {
- newmax = 16;
- owner->dsms = (dsm_segment **)
- MemoryContextAlloc(TopMemoryContext,
- newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
- else
- {
- newmax = owner->maxdsms * 2;
- owner->dsms = (dsm_segment **)
- repalloc(owner->dsms, newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
+ ResourceArrayEnlarge(&(owner->dsmarr));
}
/*
@@ -1295,9 +1258,7 @@ ResourceOwnerEnlargeDSMs(ResourceOwner owner)
void
ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
{
- Assert(owner->ndsms < owner->maxdsms);
- owner->dsms[owner->ndsms] = seg;
- owner->ndsms++;
+ ResourceArrayAdd(&(owner->dsmarr), (const uint8*)&seg);
}
/*
@@ -1306,26 +1267,10 @@ ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
void
ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
{
- dsm_segment **dsms = owner->dsms;
- int ns1 = owner->ndsms - 1;
- int i;
-
- for (i = ns1; i >= 0; i--)
- {
- if (dsms[i] == seg)
- {
- while (i < ns1)
- {
- dsms[i] = dsms[i + 1];
- i++;
- }
- owner->ndsms = ns1;
- return;
- }
- }
- elog(ERROR,
- "dynamic shared memory segment %u is not owned by resource owner %s",
- dsm_segment_handle(seg), owner->name);
+ bool res = ResourceArrayRemove(&(owner->dsmarr), (const uint8*)&seg);
+ if(!res)
+ elog(ERROR, "dynamic shared memory segment %u is not owned by resource"
+ " owner %s", dsm_segment_handle(seg), owner->name);
}
On Fri, Dec 4, 2015 at 7:15 AM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:
Current implementation of ResourceOwner uses arrays to store resources
like TupleDescs, Snapshots, etc. When we want to release one of these
resources ResourceOwner finds it with linear scan. Granted, resource
array are usually small so its not a problem most of the time. But it
appears to be a bottleneck when we are working with tables which have a
lot of partitions.To reproduce this issue:
1. run `./gen.pl 10000 | psql my_database postgres`
2. run `pgbench -j 8 -c 8 -f q.sql -T 100 my_database`
3. in second terminal run `sudo perf top -u postgres`Both gen.pl and q.sql are attached to this message.
You will see that postgres spends a lot of time in ResourceOwnerForget*
procedures:32.80% postgres [.] list_nth
20.29% postgres [.] ResourceOwnerForgetRelationRef
12.87% postgres [.] find_all_inheritors
7.90% postgres [.] get_tabstat_entry
6.68% postgres [.] ResourceOwnerForgetTupleDesc
1.17% postgres [.] hash_search_with_hash_value
... < 1% ...I would like to suggest a patch (see attachment) witch fixes this
bottleneck. Also I discovered that there is a lot of code duplication in
ResourceOwner. Patch fixes this too. The idea is quite simple. I just
replaced arrays with something that could be considered hash tables,
but with some specific optimizations.After applying this patch we can see that bottleneck is gone:
42.89% postgres [.] list_nth
18.30% postgres [.] find_all_inheritors
10.97% postgres [.] get_tabstat_entry
1.82% postgres [.] hash_search_with_hash_value
1.21% postgres [.] SearchCatCache
... < 1% ...For tables with thousands partitions we gain in average 32.5% more TPS.
As far as I can see in the same time patch doesn't make anything worse.
`make check` passes with asserts enabled and disabled. There is no
performance degradation according to both standard pgbench benchmark
and benchmark described above for tables with 10 and 100 partitions.
I do think it's interesting to try to speed up the ResourceOwner
mechanism when there are many resources owned. We've had some forays
in that direction in the past, and I think it's useful to continue
that work. But I also think that this patch, while interesting, is
not something we can seriously consider committing in its current
form, because:
1. It lacks adequate comments and submission notes to explain the
general theory of operation of the patch.
2. It seems to use uint8 * rather freely as a substitute for char * or
void *, which doesn't seem like a great idea.
3. It doesn't follow PostgreSQL formatting conventions (uncuddled
curly braces, space after if and before open paren, etc.).
4. It mixes together multiple ideas in a single patch, not only
introducing a hashing concept but also striping a brand-new layer of
abstraction across the resource-owner mechanism. I am not sure that
layer of abstraction is a very good idea, but if it needs to be done,
I think it should be a separate patch.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello, Robert
Thanks for your review. I believe I fixed items 1, 2 and 3 (see
attachment). Also I would like to clarify item 4.
4. It mixes together multiple ideas in a single patch, not only
introducing a hashing concept but also striping a brand-new layer of
abstraction across the resource-owner mechanism. I am not sure that
layer of abstraction is a very good idea, but if it needs to be done,
I think it should be a separate patch.
Do I right understand that you suggest following?
Current patch should be split in two parts. In first patch we create
and use ResourceArray with array-based implementation (abstraction
layer). Then we apply second patch which change ResourceArray
implementation to hashing based (optimization).
Best regards,
Aleksander
On Tue, 8 Dec 2015 17:30:33 -0500
Robert Haas <robertmhaas@gmail.com> wrote:
Show quoted text
On Fri, Dec 4, 2015 at 7:15 AM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:Current implementation of ResourceOwner uses arrays to store
resources like TupleDescs, Snapshots, etc. When we want to release
one of these resources ResourceOwner finds it with linear scan.
Granted, resource array are usually small so its not a problem most
of the time. But it appears to be a bottleneck when we are working
with tables which have a lot of partitions.To reproduce this issue:
1. run `./gen.pl 10000 | psql my_database postgres`
2. run `pgbench -j 8 -c 8 -f q.sql -T 100 my_database`
3. in second terminal run `sudo perf top -u postgres`Both gen.pl and q.sql are attached to this message.
You will see that postgres spends a lot of time in
ResourceOwnerForget* procedures:32.80% postgres [.] list_nth
20.29% postgres [.] ResourceOwnerForgetRelationRef
12.87% postgres [.] find_all_inheritors
7.90% postgres [.] get_tabstat_entry
6.68% postgres [.] ResourceOwnerForgetTupleDesc
1.17% postgres [.] hash_search_with_hash_value
... < 1% ...I would like to suggest a patch (see attachment) witch fixes this
bottleneck. Also I discovered that there is a lot of code
duplication in ResourceOwner. Patch fixes this too. The idea is
quite simple. I just replaced arrays with something that could be
considered hash tables, but with some specific optimizations.After applying this patch we can see that bottleneck is gone:
42.89% postgres [.] list_nth
18.30% postgres [.] find_all_inheritors
10.97% postgres [.] get_tabstat_entry
1.82% postgres [.] hash_search_with_hash_value
1.21% postgres [.] SearchCatCache
... < 1% ...For tables with thousands partitions we gain in average 32.5% more
TPS.As far as I can see in the same time patch doesn't make anything
worse. `make check` passes with asserts enabled and disabled. There
is no performance degradation according to both standard pgbench
benchmark and benchmark described above for tables with 10 and 100
partitions.I do think it's interesting to try to speed up the ResourceOwner
mechanism when there are many resources owned. We've had some forays
in that direction in the past, and I think it's useful to continue
that work. But I also think that this patch, while interesting, is
not something we can seriously consider committing in its current
form, because:1. It lacks adequate comments and submission notes to explain the
general theory of operation of the patch.2. It seems to use uint8 * rather freely as a substitute for char * or
void *, which doesn't seem like a great idea.3. It doesn't follow PostgreSQL formatting conventions (uncuddled
curly braces, space after if and before open paren, etc.).4. It mixes together multiple ideas in a single patch, not only
introducing a hashing concept but also striping a brand-new layer of
abstraction across the resource-owner mechanism. I am not sure that
layer of abstraction is a very good idea, but if it needs to be done,
I think it should be a separate patch.
Attachments:
resource-owner-optimization-v2.patchtext/x-patchDownload
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 0e7acbf..d2b37d5 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -29,6 +29,323 @@
#include "utils/snapmgr.h"
/*
+ * ResourceArray is a common structure for storing different types of resources.
+ *
+ * ResourceOwner can own `HeapTuple`s, `Relation`s, `Snapshot`s, etc. For
+ * each resource type there are procedures ResourceOwnerRemember* and
+ * ResourceOwnerForget*. There are also ResourceOwnerEnlarge* procedures
+ * which should be called before corresponding ResourceOwnerRemember* calls
+ * (see below). Internally each type of resource is stored in separate
+ * ResourceArray.
+ *
+ * There are two major reasons for using ResourceArray instead of, say,
+ * regular C arrays.
+ *
+ * Firstly we would like to prevent code duplication. For instance
+ * ResourceArray provides generic Remember/Forget/Enlarge procedures, so
+ * corresponding ResourceOwner* procedures are just a typesafe wrappers for
+ * these procedures. Note that different resources have different sizeof. Thus
+ * ResourceArray should know size of resource and store all data in char[]
+ * buffer.
+ *
+ * Secondly ResourceArray must be more efficient than regular C array.
+ * Previous implementation of ResourceOwner used arrays. It had O(1) complexity
+ * of Remember procedures and O(N) complexity of Forget procedures where N is a
+ * number of remembered resources. It turned out that such implementation
+ * creates a bottleneck in some cases, e.g. when working with partitioned
+ * tables which have a lot of (say, thousands) partitions. New implementation
+ * in general could be considered a hash table with some specific optimizations.
+ * It has O(1) complexity of both Remember and Forget procedures and
+ * apparently doesn't cause any performance degradation compared to previous
+ * implementation.
+ */
+typedef struct ResourceArray
+{
+ char *itemsarr; /* buffer for storing values */
+ uint32 itemsizelg:2; /* sizeof one item log 2 */
+ uint32 capacity:30; /* capacity of array */
+ uint32 nitems; /* how many items is stored in items array */
+ uint32 maxitems; /* precalculated RESARRAY_MAX_ITEMS(capacity) */
+} ResourceArray;
+
+/*
+ * This number is used as initial size of resource array. If given number of
+ * items is not enough, we double array size and reallocate memory.
+ *
+ * Should be power of two since we use (arrsize -1) as mask for hash value.
+ *
+ */
+#define RESARRAY_INIT_SIZE 16
+
+/*
+ * How many items could be stored in a resource array of given capacity. If
+ * this number is reached we need to resize an array to prevent hash collisions.
+ *
+ * This computation actually costs only two additions and one binary shift.
+ */
+#define RESARRAY_MAX_ITEMS(capacity) ((capacity)*3/4)
+
+/*
+ * Such type of callback function is called when resource stored in
+ * ResourceArray is released using ResourceArrayFree. Callback should
+ * _actually_ release a resource so nitems value will be decremented.
+ */
+typedef void (*ResourceArrayRemoveCallback) (const void *ref, bool isCommit);
+
+/* Used as argument to memcmp to determine if ResourceArray[i] is free. */
+static const char RESOURCE_ARRAY_ZERO_ELEMENT[sizeof(void *)] =
+{
+ 0
+};
+
+/*
+ * Calculate hash_any of given data. For uint32 values use faster hash_uint32.
+ */
+static Datum
+ResourceArrayHash(const void *data, int size)
+{
+ uint32 tmp;
+
+ Assert(size == sizeof(uint32) || size == sizeof(void *));
+
+ if (size == sizeof(uint32))
+ {
+ tmp = *((const uint32 *) data);
+ return hash_uint32(tmp);
+ }
+ else
+ {
+ return hash_any(data, size);
+ }
+}
+
+/*
+ * Initialize ResourceArray
+ */
+static void
+ResourceArrayInit(ResourceArray * resarr, uint32 itemsize)
+{
+ Assert(itemsize == sizeof(int) || itemsize == sizeof(void *));
+ Assert(resarr->itemsarr == NULL);
+ Assert(resarr->capacity == 0);
+ Assert(resarr->nitems == 0);
+ Assert(resarr->maxitems == 0);
+
+ resarr->itemsizelg = 0;
+ while (itemsize > 1)
+ {
+ resarr->itemsizelg++;
+ itemsize >>= 1;
+ }
+
+ Assert(resarr->itemsizelg == 2 || resarr->itemsizelg == 3);
+}
+
+/*
+ * Add a resource to ResourceArray
+ *
+ * Caller must have previously done ResourceArrayEnlarge()
+ */
+static void
+ResourceArrayAdd(ResourceArray * resarr, const void *dataref)
+{
+ char *itemptr;
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(memcmp(dataref, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0);
+ Assert(resarr->maxitems > resarr->nitems);
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+
+ /*
+ * Hashing is quite expensive, so we use it only for large arrays. For
+ * small arrays we just use a linear scan.
+ */
+ if (resarr->capacity == RESARRAY_INIT_SIZE)
+ idx = resarr->nitems;
+ else
+ idx = ResourceArrayHash(dataref, itemsize);
+ idx &= mask;
+
+ while (true)
+ {
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is
+ * always power of two. It would use multiplication instead of
+ * efficient binary shift in code `idx * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if (memcmp(itemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) == 0)
+ break;
+ idx = (idx + 1) & mask;
+ }
+
+ memcpy(itemptr, dataref, itemsize);
+ resarr->nitems++;
+}
+
+/*
+ * Remove a resource from ResourceArray
+ *
+ * Returns true on success, false if resource was not found
+ */
+static bool
+ResourceArrayRemove(ResourceArray * resarr, const void *dataref)
+{
+ char *itemptr;
+ Datum idx;
+ uint32 i;
+ Datum mask = resarr->capacity - 1;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(memcmp(dataref, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0);
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+
+ /*
+ * Hashing is quite expensive, so we use it only for large arrays. For
+ * small arrays we use a linear scan.
+ */
+ if (resarr->capacity == RESARRAY_INIT_SIZE)
+ idx = 0;
+ else
+ idx = ResourceArrayHash(dataref, itemsize);
+ idx &= mask;
+
+ for (i = 0; i < resarr->capacity; i++)
+ {
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is
+ * always power of two. It would use multiplication instead of
+ * efficient binary shift in code `idx * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if (memcmp(itemptr, dataref, itemsize) == 0)
+ {
+ memset(itemptr, 0, itemsize);
+ resarr->nitems--;
+ return true;
+ }
+ idx = (idx + 1) & mask;
+ }
+
+ return false;
+}
+
+/*
+ * Make sure there is a room for at least one more resource in an array.
+ *
+ * This is separate from actually inserting a resource because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+static void
+ResourceArrayEnlarge(ResourceArray * resarr)
+{
+ uint32 oldcap;
+ char *olditemsarr;
+ char *olditemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(resarr->itemsizelg != 0);
+
+ if (resarr->nitems < resarr->maxitems)
+ return; /* nothing to do */
+
+ olditemsarr = resarr->itemsarr;
+ oldcap = resarr->capacity;
+
+ resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
+ resarr->itemsarr = (char *)
+ MemoryContextAllocZero(TopMemoryContext,
+ resarr->capacity * itemsize);
+ resarr->maxitems = RESARRAY_MAX_ITEMS(resarr->capacity);
+ resarr->nitems = 0;
+
+ if (olditemsarr != NULL)
+ {
+ while (oldcap > 0)
+ {
+ oldcap--;
+
+ /*
+ * Read next line as:
+ *
+ * olditemptr = &(olditemsarr[oldcap])
+ *
+ * We use binary shift since compiler doesn't know that itemsize
+ * is always power of two. It would use multiplication instead of
+ * efficient binary shift in code `oldcap * itemsize`.
+ */
+ olditemptr = olditemsarr + (oldcap << resarr->itemsizelg);
+ if (memcmp(olditemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0)
+ ResourceArrayAdd(resarr, olditemptr);
+ }
+ pfree(olditemsarr);
+ }
+}
+
+/*
+ * Remove all resources in array and call a callback function for each release.
+ */
+static void
+ResourceArrayRemoveAll(ResourceArray * resarr,
+ ResourceArrayRemoveCallback releasecb,
+ bool isCommit)
+{
+ uint32 idx = 0;
+ char *itemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ while (resarr->nitems > 0)
+ {
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is
+ * always power of two. It would use multiplication instead of
+ * efficient binary shift in code `idx * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if (memcmp(itemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) == 0)
+ {
+ idx++;
+ continue;
+ }
+ releasecb(itemptr, isCommit);
+ }
+}
+
+/*
+ * Return ResourceArray to initial state
+ */
+static void
+ResourceArrayFree(ResourceArray * resarr)
+{
+ Assert(resarr->nitems == 0);
+
+ resarr->capacity = 0;
+ resarr->maxitems = 0;
+
+ if (!resarr->itemsarr)
+ return;
+
+ pfree(resarr->itemsarr);
+ resarr->itemsarr = NULL;
+}
+
+/*
* To speed up bulk releasing or reassigning locks from a resource owner to
* its parent, each resource owner has a small cache of locks it owns. The
* lock manager has the same information in its local lock hash table, and
@@ -56,53 +373,22 @@ typedef struct ResourceOwnerData
ResourceOwner nextchild; /* next child of same parent */
const char *name; /* name (just for debugging) */
- /* We have built-in support for remembering owned buffers */
- int nbuffers; /* number of owned buffer pins */
- Buffer *buffers; /* dynamically allocated array */
- int maxbuffers; /* currently allocated array size */
-
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
int nlocks; /* number of owned locks */
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
- /* We have built-in support for remembering catcache references */
- int ncatrefs; /* number of owned catcache pins */
- HeapTuple *catrefs; /* dynamically allocated array */
- int maxcatrefs; /* currently allocated array size */
-
- int ncatlistrefs; /* number of owned catcache-list pins */
- CatCList **catlistrefs; /* dynamically allocated array */
- int maxcatlistrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering relcache references */
- int nrelrefs; /* number of owned relcache pins */
- Relation *relrefs; /* dynamically allocated array */
- int maxrelrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering plancache references */
- int nplanrefs; /* number of owned plancache pins */
- CachedPlan **planrefs; /* dynamically allocated array */
- int maxplanrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering tupdesc references */
- int ntupdescs; /* number of owned tupdesc references */
- TupleDesc *tupdescs; /* dynamically allocated array */
- int maxtupdescs; /* currently allocated array size */
-
- /* We have built-in support for remembering snapshot references */
- int nsnapshots; /* number of owned snapshot references */
- Snapshot *snapshots; /* dynamically allocated array */
- int maxsnapshots; /* currently allocated array size */
-
- /* We have built-in support for remembering open temporary files */
- int nfiles; /* number of owned temporary files */
- File *files; /* dynamically allocated array */
- int maxfiles; /* currently allocated array size */
-
- /* We have built-in support for remembering dynamic shmem segments */
- int ndsms; /* number of owned shmem segments */
- dsm_segment **dsms; /* dynamically allocated array */
- int maxdsms; /* currently allocated array size */
+ /* We have built-in support for remembering: */
+
+ ResourceArray catrefarr; /* `HeapTuple`s */
+ ResourceArray catlistrefarr; /* `ResourceOwner`s */
+ ResourceArray relrefarr; /* `Relation`s */
+ ResourceArray planrefarr; /* `CachedPlan*`s */
+ ResourceArray tupdescarr; /* `TupleDesc`s */
+ ResourceArray snapshotarr; /* `Snapshot`s */
+ ResourceArray dsmarr; /* `dsm_segment*`s */
+ ResourceArray bufferarr; /* `Buffer`s */
+ ResourceArray filearr; /* `File`s */
+
} ResourceOwnerData;
@@ -168,6 +454,16 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
+ ResourceArrayInit(&(owner->catrefarr), sizeof(HeapTuple));
+ ResourceArrayInit(&(owner->catlistrefarr), sizeof(ResourceOwner));
+ ResourceArrayInit(&(owner->relrefarr), sizeof(Relation));
+ ResourceArrayInit(&(owner->planrefarr), sizeof(CachedPlan *));
+ ResourceArrayInit(&(owner->tupdescarr), sizeof(TupleDesc));
+ ResourceArrayInit(&(owner->snapshotarr), sizeof(Snapshot));
+ ResourceArrayInit(&(owner->dsmarr), sizeof(dsm_segment *));
+ ResourceArrayInit(&(owner->bufferarr), sizeof(Buffer));
+ ResourceArrayInit(&(owner->filearr), sizeof(File));
+
return owner;
}
@@ -221,6 +517,96 @@ ResourceOwnerRelease(ResourceOwner owner,
}
static void
+ReleaseCachedPlanCallback(const void *dataref, bool isCommit)
+{
+ CachedPlan *res = *((CachedPlan **) dataref);
+
+ if (isCommit)
+ PrintPlanCacheLeakWarning(res);
+ ReleaseCachedPlan(res, true);
+}
+
+static void
+ReleaseDSMCallback(const void *dataref, bool isCommit)
+{
+ dsm_segment *res = *((dsm_segment **) dataref);
+
+ if (isCommit)
+ PrintDSMLeakWarning(res);
+ dsm_detach(res);
+}
+
+static void
+ReleaseTupleDescCallback(const void *dataref, bool isCommit)
+{
+ TupleDesc res = *((TupleDesc *) dataref);
+
+ if (isCommit)
+ PrintTupleDescLeakWarning(res);
+ DecrTupleDescRefCount(res);
+}
+
+static void
+ReleaseSnapshotCallback(const void *dataref, bool isCommit)
+{
+ Snapshot res = *((Snapshot *) dataref);
+
+ if (isCommit)
+ PrintSnapshotLeakWarning(res);
+ UnregisterSnapshot(res);
+}
+
+static void
+ReleaseRelationCallback(const void *dataref, bool isCommit)
+{
+ Relation res = *((Relation *) dataref);
+
+ if (isCommit)
+ PrintRelCacheLeakWarning(res);
+ RelationClose(res);
+}
+
+static void
+ReleaseCatCacheListCallback(const void *dataref, bool isCommit)
+{
+ CatCList *res = *((CatCList **) dataref);
+
+ if (isCommit)
+ PrintCatCacheListLeakWarning(res);
+ ReleaseCatCacheList(res);
+}
+
+static void
+ReleaseCatCacheCallback(const void *dataref, bool isCommit)
+{
+ HeapTuple res = *((HeapTuple *) dataref);
+
+ if (isCommit)
+ PrintCatCacheLeakWarning(res);
+ ReleaseCatCache(res);
+}
+
+static void
+ReleaseBufferCallback(const void *dataref, bool isCommit)
+{
+ Buffer res = *((Buffer *) dataref);
+
+ if (isCommit)
+ PrintBufferLeakWarning(res);
+ ReleaseBuffer(res);
+}
+
+static void
+ReleaseFileCallback(const void *dataref, bool isCommit)
+{
+ File res = *((File *) dataref);
+
+ if (isCommit)
+ PrintFileLeakWarning(res);
+ FileClose(res);
+}
+
+static void
ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceReleasePhase phase,
bool isCommit,
@@ -252,46 +638,17 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* During a commit, there shouldn't be any remaining pins --- that
* would indicate failure to clean up the executor correctly --- so
* issue warnings. In the abort case, just clean up quietly.
- *
- * We are careful to do the releasing back-to-front, so as to avoid
- * O(N^2) behavior in ResourceOwnerForgetBuffer().
*/
- while (owner->nbuffers > 0)
- {
- if (isCommit)
- PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
- ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->bufferarr),
+ ReleaseBufferCallback, isCommit);
- /*
- * Release relcache references. Note that RelationClose will remove
- * the relref entry from my list, so I just have to iterate till there
- * are none.
- *
- * As with buffer pins, warn if any are left at commit time, and
- * release back-to-front for speed.
- */
- while (owner->nrelrefs > 0)
- {
- if (isCommit)
- PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]);
- RelationClose(owner->relrefs[owner->nrelrefs - 1]);
- }
+ /* Ditto for relcache references. */
+ ResourceArrayRemoveAll(&(owner->relrefarr),
+ ReleaseRelationCallback, isCommit);
- /*
- * Release dynamic shared memory segments. Note that dsm_detach()
- * will remove the segment from my list, so I just have to iterate
- * until there are none.
- *
- * As in the preceding cases, warn if there are leftover at commit
- * time.
- */
- while (owner->ndsms > 0)
- {
- if (isCommit)
- PrintDSMLeakWarning(owner->dsms[owner->ndsms - 1]);
- dsm_detach(owner->dsms[owner->ndsms - 1]);
- }
+ /* Ditto for dynamic shared memory segments */
+ ResourceArrayRemoveAll(&(owner->dsmarr),
+ ReleaseDSMCallback, isCommit);
}
else if (phase == RESOURCE_RELEASE_LOCKS)
{
@@ -351,48 +708,28 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
- while (owner->ncatrefs > 0)
- {
- if (isCommit)
- PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]);
- ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->catrefarr),
+ ReleaseCatCacheCallback, isCommit);
+
/* Ditto for catcache lists */
- while (owner->ncatlistrefs > 0)
- {
- if (isCommit)
- PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
- ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->catlistrefarr),
+ ReleaseCatCacheListCallback, isCommit);
+
/* Ditto for plancache references */
- while (owner->nplanrefs > 0)
- {
- if (isCommit)
- PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
- ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
- }
+ ResourceArrayRemoveAll(&(owner->planrefarr),
+ ReleaseCachedPlanCallback, isCommit);
+
/* Ditto for tupdesc references */
- while (owner->ntupdescs > 0)
- {
- if (isCommit)
- PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
- DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->tupdescarr),
+ ReleaseTupleDescCallback, isCommit);
+
/* Ditto for snapshot references */
- while (owner->nsnapshots > 0)
- {
- if (isCommit)
- PrintSnapshotLeakWarning(owner->snapshots[owner->nsnapshots - 1]);
- UnregisterSnapshot(owner->snapshots[owner->nsnapshots - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->snapshotarr),
+ ReleaseSnapshotCallback, isCommit);
/* Ditto for temporary files */
- while (owner->nfiles > 0)
- {
- if (isCommit)
- PrintFileLeakWarning(owner->files[owner->nfiles - 1]);
- FileClose(owner->files[owner->nfiles - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->filearr),
+ ReleaseFileCallback, isCommit);
/* Clean up index scans too */
ReleaseResources_hash();
@@ -418,16 +755,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner != CurrentResourceOwner);
/* And it better not own any resources, either */
- Assert(owner->nbuffers == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
- Assert(owner->ncatrefs == 0);
- Assert(owner->ncatlistrefs == 0);
- Assert(owner->nrelrefs == 0);
- Assert(owner->ndsms == 0);
- Assert(owner->nplanrefs == 0);
- Assert(owner->ntupdescs == 0);
- Assert(owner->nsnapshots == 0);
- Assert(owner->nfiles == 0);
/*
* Delete children. The recursive call will delink the child from me, so
@@ -444,25 +772,15 @@ ResourceOwnerDelete(ResourceOwner owner)
ResourceOwnerNewParent(owner, NULL);
/* And free the object. */
- if (owner->buffers)
- pfree(owner->buffers);
- if (owner->catrefs)
- pfree(owner->catrefs);
- if (owner->catlistrefs)
- pfree(owner->catlistrefs);
- if (owner->relrefs)
- pfree(owner->relrefs);
- if (owner->planrefs)
- pfree(owner->planrefs);
- if (owner->tupdescs)
- pfree(owner->tupdescs);
- if (owner->snapshots)
- pfree(owner->snapshots);
- if (owner->files)
- pfree(owner->files);
- if (owner->dsms)
- pfree(owner->dsms);
-
+ ResourceArrayFree(&(owner->catrefarr));
+ ResourceArrayFree(&(owner->catlistrefarr));
+ ResourceArrayFree(&(owner->relrefarr));
+ ResourceArrayFree(&(owner->planrefarr));
+ ResourceArrayFree(&(owner->tupdescarr));
+ ResourceArrayFree(&(owner->snapshotarr));
+ ResourceArrayFree(&(owner->dsmarr));
+ ResourceArrayFree(&(owner->bufferarr));
+ ResourceArrayFree(&(owner->filearr));
pfree(owner);
}
@@ -575,26 +893,9 @@ UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
void
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
{
- int newmax;
-
- if (owner == NULL ||
- owner->nbuffers < owner->maxbuffers)
- return; /* nothing to do */
-
- if (owner->buffers == NULL)
- {
- newmax = 16;
- owner->buffers = (Buffer *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
- else
- {
- newmax = owner->maxbuffers * 2;
- owner->buffers = (Buffer *)
- repalloc(owner->buffers, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayEnlarge(&(owner->bufferarr));
}
/*
@@ -608,12 +909,9 @@ ResourceOwnerEnlargeBuffers(ResourceOwner owner)
void
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Assert(owner->nbuffers < owner->maxbuffers);
- owner->buffers[owner->nbuffers] = buffer;
- owner->nbuffers++;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayAdd(&(owner->bufferarr), (const void *) &buffer);
}
/*
@@ -625,33 +923,15 @@ ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Buffer *buffers = owner->buffers;
- int nb1 = owner->nbuffers - 1;
- int i;
+ bool res;
- /*
- * Scan back-to-front because it's more likely we are releasing a
- * recently pinned buffer. This isn't always the case of course, but
- * it's the way to bet.
- */
- for (i = nb1; i >= 0; i--)
- {
- if (buffers[i] == buffer)
- {
- while (i < nb1)
- {
- buffers[i] = buffers[i + 1];
- i++;
- }
- owner->nbuffers = nb1;
- return;
- }
- }
+ if (owner == NULL)
+ return;
+
+ res = ResourceArrayRemove(&(owner->bufferarr), (const void *) &buffer);
+ if (!res)
elog(ERROR, "buffer %d is not owned by resource owner %s",
buffer, owner->name);
- }
}
/*
@@ -667,6 +947,8 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
{
+ Assert(locallock != NULL);
+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have already overflowed */
@@ -714,25 +996,7 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
void
ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatrefs < owner->maxcatrefs)
- return; /* nothing to do */
-
- if (owner->catrefs == NULL)
- {
- newmax = 16;
- owner->catrefs = (HeapTuple *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatrefs * 2;
- owner->catrefs = (HeapTuple *)
- repalloc(owner->catrefs, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catrefarr));
}
/*
@@ -743,9 +1007,7 @@ ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- Assert(owner->ncatrefs < owner->maxcatrefs);
- owner->catrefs[owner->ncatrefs] = tuple;
- owner->ncatrefs++;
+ ResourceArrayAdd(&(owner->catrefarr), (const void *) &tuple);
}
/*
@@ -754,25 +1016,12 @@ ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- HeapTuple *catrefs = owner->catrefs;
- int nc1 = owner->ncatrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catrefarr),
+ (const void *) &tuple);
- for (i = nc1; i >= 0; i--)
- {
- if (catrefs[i] == tuple)
- {
- while (i < nc1)
- {
- catrefs[i] = catrefs[i + 1];
- i++;
- }
- owner->ncatrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache reference %p is not owned by resource owner %s",
- tuple, owner->name);
+ if (!res)
+ elog(ERROR, "catcache reference %p is not owned by resource owner %s",
+ tuple, owner->name);
}
/*
@@ -785,25 +1034,7 @@ ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatlistrefs < owner->maxcatlistrefs)
- return; /* nothing to do */
-
- if (owner->catlistrefs == NULL)
- {
- newmax = 16;
- owner->catlistrefs = (CatCList **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatlistrefs * 2;
- owner->catlistrefs = (CatCList **)
- repalloc(owner->catlistrefs, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catlistrefarr));
}
/*
@@ -814,9 +1045,7 @@ ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- Assert(owner->ncatlistrefs < owner->maxcatlistrefs);
- owner->catlistrefs[owner->ncatlistrefs] = list;
- owner->ncatlistrefs++;
+ ResourceArrayAdd(&(owner->catlistrefarr), (const void *) &list);
}
/*
@@ -825,25 +1054,12 @@ ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- CatCList **catlistrefs = owner->catlistrefs;
- int nc1 = owner->ncatlistrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catlistrefarr),
+ (const void *) &list);
- for (i = nc1; i >= 0; i--)
- {
- if (catlistrefs[i] == list)
- {
- while (i < nc1)
- {
- catlistrefs[i] = catlistrefs[i + 1];
- i++;
- }
- owner->ncatlistrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
- list, owner->name);
+ if (!res)
+ elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
+ list, owner->name);
}
/*
@@ -856,25 +1072,7 @@ ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nrelrefs < owner->maxrelrefs)
- return; /* nothing to do */
-
- if (owner->relrefs == NULL)
- {
- newmax = 16;
- owner->relrefs = (Relation *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
- else
- {
- newmax = owner->maxrelrefs * 2;
- owner->relrefs = (Relation *)
- repalloc(owner->relrefs, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->relrefarr));
}
/*
@@ -885,9 +1083,7 @@ ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
- Assert(owner->nrelrefs < owner->maxrelrefs);
- owner->relrefs[owner->nrelrefs] = rel;
- owner->nrelrefs++;
+ ResourceArrayAdd(&(owner->relrefarr), (const void *) &rel);
}
/*
@@ -896,25 +1092,12 @@ ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
- Relation *relrefs = owner->relrefs;
- int nr1 = owner->nrelrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->relrefarr),
+ (const void *) &rel);
- for (i = nr1; i >= 0; i--)
- {
- if (relrefs[i] == rel)
- {
- while (i < nr1)
- {
- relrefs[i] = relrefs[i + 1];
- i++;
- }
- owner->nrelrefs = nr1;
- return;
- }
- }
- elog(ERROR, "relcache reference %s is not owned by resource owner %s",
- RelationGetRelationName(rel), owner->name);
+ if (!res)
+ elog(ERROR, "relcache reference %s is not owned by resource owner %s",
+ RelationGetRelationName(rel), owner->name);
}
/*
@@ -937,25 +1120,7 @@ PrintRelCacheLeakWarning(Relation rel)
void
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nplanrefs < owner->maxplanrefs)
- return; /* nothing to do */
-
- if (owner->planrefs == NULL)
- {
- newmax = 16;
- owner->planrefs = (CachedPlan **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
- else
- {
- newmax = owner->maxplanrefs * 2;
- owner->planrefs = (CachedPlan **)
- repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->planrefarr));
}
/*
@@ -966,9 +1131,7 @@ ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- Assert(owner->nplanrefs < owner->maxplanrefs);
- owner->planrefs[owner->nplanrefs] = plan;
- owner->nplanrefs++;
+ ResourceArrayAdd(&(owner->planrefarr), (const void *) &plan);
}
/*
@@ -977,25 +1140,12 @@ ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
void
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- CachedPlan **planrefs = owner->planrefs;
- int np1 = owner->nplanrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->planrefarr),
+ (const void *) &plan);
- for (i = np1; i >= 0; i--)
- {
- if (planrefs[i] == plan)
- {
- while (i < np1)
- {
- planrefs[i] = planrefs[i + 1];
- i++;
- }
- owner->nplanrefs = np1;
- return;
- }
- }
- elog(ERROR, "plancache reference %p is not owned by resource owner %s",
- plan, owner->name);
+ if (!res)
+ elog(ERROR, "plancache reference %p is not owned by resource owner %s",
+ plan, owner->name);
}
/*
@@ -1017,25 +1167,7 @@ PrintPlanCacheLeakWarning(CachedPlan *plan)
void
ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ntupdescs < owner->maxtupdescs)
- return; /* nothing to do */
-
- if (owner->tupdescs == NULL)
- {
- newmax = 16;
- owner->tupdescs = (TupleDesc *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
- else
- {
- newmax = owner->maxtupdescs * 2;
- owner->tupdescs = (TupleDesc *)
- repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->tupdescarr));
}
/*
@@ -1046,9 +1178,7 @@ ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
void
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- Assert(owner->ntupdescs < owner->maxtupdescs);
- owner->tupdescs[owner->ntupdescs] = tupdesc;
- owner->ntupdescs++;
+ ResourceArrayAdd(&(owner->tupdescarr), (const void *) &tupdesc);
}
/*
@@ -1057,25 +1187,12 @@ ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
void
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- TupleDesc *tupdescs = owner->tupdescs;
- int nt1 = owner->ntupdescs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->tupdescarr),
+ (const void *) &tupdesc);
- for (i = nt1; i >= 0; i--)
- {
- if (tupdescs[i] == tupdesc)
- {
- while (i < nt1)
- {
- tupdescs[i] = tupdescs[i + 1];
- i++;
- }
- owner->ntupdescs = nt1;
- return;
- }
- }
- elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
- tupdesc, owner->name);
+ if (!res)
+ elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
+ tupdesc, owner->name);
}
/*
@@ -1099,25 +1216,7 @@ PrintTupleDescLeakWarning(TupleDesc tupdesc)
void
ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nsnapshots < owner->maxsnapshots)
- return; /* nothing to do */
-
- if (owner->snapshots == NULL)
- {
- newmax = 16;
- owner->snapshots = (Snapshot *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
- else
- {
- newmax = owner->maxsnapshots * 2;
- owner->snapshots = (Snapshot *)
- repalloc(owner->snapshots, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
+ ResourceArrayEnlarge(&(owner->snapshotarr));
}
/*
@@ -1128,9 +1227,7 @@ ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
void
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Assert(owner->nsnapshots < owner->maxsnapshots);
- owner->snapshots[owner->nsnapshots] = snapshot;
- owner->nsnapshots++;
+ ResourceArrayAdd(&(owner->snapshotarr), (const void *) &snapshot);
}
/*
@@ -1139,25 +1236,12 @@ ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
void
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Snapshot *snapshots = owner->snapshots;
- int ns1 = owner->nsnapshots - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->snapshotarr),
+ (const void *) &snapshot);
- for (i = ns1; i >= 0; i--)
- {
- if (snapshots[i] == snapshot)
- {
- while (i < ns1)
- {
- snapshots[i] = snapshots[i + 1];
- i++;
- }
- owner->nsnapshots = ns1;
- return;
- }
- }
- elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
- snapshot, owner->name);
+ if (!res)
+ elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
+ snapshot, owner->name);
}
/*
@@ -1182,25 +1266,7 @@ PrintSnapshotLeakWarning(Snapshot snapshot)
void
ResourceOwnerEnlargeFiles(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nfiles < owner->maxfiles)
- return; /* nothing to do */
-
- if (owner->files == NULL)
- {
- newmax = 16;
- owner->files = (File *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
- else
- {
- newmax = owner->maxfiles * 2;
- owner->files = (File *)
- repalloc(owner->files, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
+ ResourceArrayEnlarge(&(owner->filearr));
}
/*
@@ -1211,9 +1277,7 @@ ResourceOwnerEnlargeFiles(ResourceOwner owner)
void
ResourceOwnerRememberFile(ResourceOwner owner, File file)
{
- Assert(owner->nfiles < owner->maxfiles);
- owner->files[owner->nfiles] = file;
- owner->nfiles++;
+ ResourceArrayAdd(&(owner->filearr), (const void *) &file);
}
/*
@@ -1222,25 +1286,12 @@ ResourceOwnerRememberFile(ResourceOwner owner, File file)
void
ResourceOwnerForgetFile(ResourceOwner owner, File file)
{
- File *files = owner->files;
- int ns1 = owner->nfiles - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->filearr),
+ (const void *) &file);
- for (i = ns1; i >= 0; i--)
- {
- if (files[i] == file)
- {
- while (i < ns1)
- {
- files[i] = files[i + 1];
- i++;
- }
- owner->nfiles = ns1;
- return;
- }
- }
- elog(ERROR, "temporery file %d is not owned by resource owner %s",
- file, owner->name);
+ if (!res)
+ elog(ERROR, "temporery file %d is not owned by resource owner %s",
+ file, owner->name);
}
@@ -1265,26 +1316,7 @@ PrintFileLeakWarning(File file)
void
ResourceOwnerEnlargeDSMs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ndsms < owner->maxdsms)
- return; /* nothing to do */
-
- if (owner->dsms == NULL)
- {
- newmax = 16;
- owner->dsms = (dsm_segment **)
- MemoryContextAlloc(TopMemoryContext,
- newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
- else
- {
- newmax = owner->maxdsms * 2;
- owner->dsms = (dsm_segment **)
- repalloc(owner->dsms, newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
+ ResourceArrayEnlarge(&(owner->dsmarr));
}
/*
@@ -1295,9 +1327,7 @@ ResourceOwnerEnlargeDSMs(ResourceOwner owner)
void
ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
{
- Assert(owner->ndsms < owner->maxdsms);
- owner->dsms[owner->ndsms] = seg;
- owner->ndsms++;
+ ResourceArrayAdd(&(owner->dsmarr), (const void *) &seg);
}
/*
@@ -1306,26 +1336,12 @@ ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
void
ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
{
- dsm_segment **dsms = owner->dsms;
- int ns1 = owner->ndsms - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->dsmarr),
+ (const void *) &seg);
- for (i = ns1; i >= 0; i--)
- {
- if (dsms[i] == seg)
- {
- while (i < ns1)
- {
- dsms[i] = dsms[i + 1];
- i++;
- }
- owner->ndsms = ns1;
- return;
- }
- }
- elog(ERROR,
- "dynamic shared memory segment %u is not owned by resource owner %s",
- dsm_segment_handle(seg), owner->name);
+ if (!res)
+ elog(ERROR, "dynamic shared memory segment %u is not owned by resource"
+ " owner %s", dsm_segment_handle(seg), owner->name);
}
On Wed, Dec 9, 2015 at 8:30 AM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:
Hello, Robert
Thanks for your review. I believe I fixed items 1, 2 and 3 (see
attachment). Also I would like to clarify item 4.4. It mixes together multiple ideas in a single patch, not only
introducing a hashing concept but also striping a brand-new layer of
abstraction across the resource-owner mechanism. I am not sure that
layer of abstraction is a very good idea, but if it needs to be done,
I think it should be a separate patch.Do I right understand that you suggest following?
Current patch should be split in two parts. In first patch we create
and use ResourceArray with array-based implementation (abstraction
layer). Then we apply second patch which change ResourceArray
implementation to hashing based (optimization).
Well, sorta. To be honest, I think this patch is really ugly. If we
were going to do this then, yes, I would want to split the patch into
two parts along those lines. But actually I don't really want to do
it this way at all. It's not that I don't want the performance
benefits: I do. But the current code is really easy to read and
extremely simple, and this changes it into something that is a heck of
a lot harder to read and understand. I'm not sure exactly what to do
about that, but it seems like a problem.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Well, sorta. To be honest, I think this patch is really ugly. If we
were going to do this then, yes, I would want to split the patch into
two parts along those lines. But actually I don't really want to do
it this way at all. It's not that I don't want the performance
benefits: I do. But the current code is really easy to read and
extremely simple, and this changes it into something that is a heck of
a lot harder to read and understand. I'm not sure exactly what to do
about that, but it seems like a problem.
I haven't read the patch in any detail, but keep in mind that part of the
API contract for ResourceOwners is that ResourceOwnerRememberFoo() cannot
fail. Period. So any patch that makes those functions less than trivial
is going to be really suspect from a reliability standpoint. It would be
advisable for example that hash_any not suddenly become covered by the
"must not fail" requirement.
BTW, I do not think you can get away with the requirement that all-zeroes
isn't a valid resource representation. It might be okay today but it's
hardly future-proof.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Thu, Dec 10, 2015 at 2:11 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Well, sorta. To be honest, I think this patch is really ugly. If we
were going to do this then, yes, I would want to split the patch into
two parts along those lines. But actually I don't really want to do
it this way at all. It's not that I don't want the performance
benefits: I do. But the current code is really easy to read and
extremely simple, and this changes it into something that is a heck of
a lot harder to read and understand. I'm not sure exactly what to do
about that, but it seems like a problem.I haven't read the patch in any detail, but keep in mind that part of the
API contract for ResourceOwners is that ResourceOwnerRememberFoo() cannot
fail. Period. So any patch that makes those functions less than trivial
is going to be really suspect from a reliability standpoint. It would be
advisable for example that hash_any not suddenly become covered by the
"must not fail" requirement.
Hmm. I hadn't thought about that.
I wonder if there's a simpler approach to this whole problem. What
the patch aims to do is allow resources to be freed in arbitrary order
without slowdown. But do we really need that? Resource owners are
currently optimized for freeing resources in the order opposite to the
order of allocation. I bet in this case the order in which they are
freed isn't random, but is exactly in order of allocation. If so, we
might be able to either (a) jigger things so that freeing in order of
allocation is efficient or (b) jigger things so that the resources are
released in reverse order of allocation as the resource owner code
expects. That might be simpler and less invasive than this fix.
Then again, it's somehow appealing for higher-level code not to have
to care about this...
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
To be honest, I think this patch is really ugly. [...] I'm not sure
exactly what to do about that, but it seems like a problem.
I have an idea. There are actually two types of resources - int-like
(buffers, files) and void*-like (RelationRef, TupleDesc, ...). What if
I split ResourceArray into IntResourceArray and PointerResourceArray? It
would definitely solve ugliness problem --- no more memcpy's, char[]
buffers, etc.
It would be advisable for example that hash_any not suddenly become
covered by the "must not fail" requirement.
Frankly I can't think of any case when hash_any could or should fail.
Maybe we should just add a "must not fail" constraint to hash_any
description?
Also I could use some other hash implementation. It may be reasonable
in this case since size of data I would like to hash is small and known
in advance.
BTW, I do not think you can get away with the requirement that
all-zeroes isn't a valid resource representation. It might be okay
today but it's hardly future-proof.
Agree. I could store a value that should be considered as "zero" in
ResourceArray. It would be InvalidBuffer for buffers, -1 for files and
NULL for all void*-types. Does such solution sounds OK?
Best regards,
Aleksander
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
It would be InvalidBuffer for buffers, -1 for files and NULL for all
void*-types. Does such solution sounds OK?
On second thought I believe there is no reason for storing anything for
void*-like types. I could just hardcode NULL in PointerResourceArray.
Best regards,
Aleksander
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello, Robert
Here is my fix for item 4.
Best regards,
Aleksander
On Thu, 10 Dec 2015 11:37:23 -0500
Robert Haas <robertmhaas@gmail.com> wrote:
Show quoted text
On Wed, Dec 9, 2015 at 8:30 AM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:Hello, Robert
Thanks for your review. I believe I fixed items 1, 2 and 3 (see
attachment). Also I would like to clarify item 4.4. It mixes together multiple ideas in a single patch, not only
introducing a hashing concept but also striping a brand-new layer
of abstraction across the resource-owner mechanism. I am not sure
that layer of abstraction is a very good idea, but if it needs to
be done, I think it should be a separate patch.Do I right understand that you suggest following?
Current patch should be split in two parts. In first patch we create
and use ResourceArray with array-based implementation (abstraction
layer). Then we apply second patch which change ResourceArray
implementation to hashing based (optimization).Well, sorta. To be honest, I think this patch is really ugly. If we
were going to do this then, yes, I would want to split the patch into
two parts along those lines. But actually I don't really want to do
it this way at all. It's not that I don't want the performance
benefits: I do. But the current code is really easy to read and
extremely simple, and this changes it into something that is a heck of
a lot harder to read and understand. I'm not sure exactly what to do
about that, but it seems like a problem.
Attachments:
resource-owner-optimization-v3-step1.patchtext/x-patchDownload
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 0e7acbf..39324fe 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -29,6 +29,209 @@
#include "utils/snapmgr.h"
/*
+ * ResourceArray is a common structure for storing different types of resources.
+ *
+ * ResourceOwner can own `HeapTuple`s, `Relation`s, `Snapshot`s, etc. For
+ * each resource type there are procedures ResourceOwnerRemember* and
+ * ResourceOwnerForget*. There are also ResourceOwnerEnlarge* procedures
+ * which should be called before corresponding ResourceOwnerRemember* calls
+ * (see below). Internally each type of resource is stored in separate
+ * ResourceArray.
+ */
+typedef struct ResourceArray
+{
+ char *itemsarr; /* buffer for storing values */
+ uint32 itemsizelg:2; /* sizeof one item log 2 */
+ uint32 capacity:30; /* capacity of array */
+ uint32 nitems; /* how many items is stored in items array */
+} ResourceArray;
+
+/*
+ * This number is used as initial size of resource array. If given number of
+ * items is not enough, we double array size and reallocate memory.
+ */
+#define RESARRAY_INIT_SIZE 16
+
+/*
+ * Such type of callback function is called when resource stored in
+ * ResourceArray is released using ResourceArrayFree. Callback should
+ * _actually_ release a resource so nitems value will be decremented.
+ */
+typedef void (*ResourceArrayRemoveCallback) (const void *ref, bool isCommit);
+
+/*
+ * Initialize ResourceArray
+ */
+static void
+ResourceArrayInit(ResourceArray * resarr, uint32 itemsize)
+{
+ Assert(itemsize == sizeof(int) || itemsize == sizeof(void *));
+ Assert(resarr->itemsarr == NULL);
+ Assert(resarr->capacity == 0);
+ Assert(resarr->nitems == 0);
+
+ resarr->itemsizelg = 0;
+ while (itemsize > 1)
+ {
+ resarr->itemsizelg++;
+ itemsize >>= 1;
+ }
+
+ Assert(resarr->itemsizelg == 2 || resarr->itemsizelg == 3);
+}
+
+/*
+ * Add a resource to ResourceArray
+ *
+ * Caller must have previously done ResourceArrayEnlarge()
+ */
+static void
+ResourceArrayAdd(ResourceArray * resarr, const void *dataref)
+{
+ char *itemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+ Assert(resarr->nitems < resarr->capacity);
+
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[resarr->nitems])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is always
+ * power of two. It would use multiplication instead of efficient binary
+ * shift in code `resarr->nitems * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (resarr->nitems << resarr->itemsizelg);
+ memcpy(itemptr, dataref, itemsize);
+ resarr->nitems++;
+}
+
+/*
+ * Remove a resource from ResourceArray
+ *
+ * Returns true on success, false if resource was not found
+ */
+static bool
+ResourceArrayRemove(ResourceArray * resarr, const void *dataref)
+{
+ Datum idx;
+ char *itemptr;
+ char *lastitemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+
+ lastitemptr = resarr->itemsarr +
+ ((resarr->nitems - 1) << resarr->itemsizelg);
+ for (idx = 0; idx < resarr->nitems; idx++)
+ {
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is
+ * always power of two. It would use multiplication instead of
+ * efficient binary shift in code `idx * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if (memcmp(itemptr, dataref, itemsize) == 0)
+ {
+ memcpy(itemptr, lastitemptr, itemsize);
+ resarr->nitems--;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make sure there is a room for at least one more resource in an array.
+ *
+ * This is separate from actually inserting a resource because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+static void
+ResourceArrayEnlarge(ResourceArray * resarr)
+{
+ uint32 oldcap;
+ char *olditemsarr;
+ char *olditemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
+ Assert(resarr->itemsizelg != 0);
+
+ if (resarr->nitems < resarr->capacity)
+ return; /* nothing to do */
+
+ olditemsarr = resarr->itemsarr;
+ oldcap = resarr->capacity;
+
+ resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
+ resarr->itemsarr = (char *)
+ MemoryContextAllocZero(TopMemoryContext,
+ resarr->capacity * itemsize);
+ resarr->nitems = 0;
+
+ if (olditemsarr != NULL)
+ {
+ while (oldcap > 0)
+ {
+ oldcap--;
+
+ /*
+ * Read next line as:
+ *
+ * olditemptr = &(olditemsarr[oldcap])
+ *
+ * We use binary shift since compiler doesn't know that itemsize
+ * is always power of two. It would use multiplication instead of
+ * efficient binary shift in code `oldcap * itemsize`.
+ */
+ olditemptr = olditemsarr + (oldcap << resarr->itemsizelg);
+ ResourceArrayAdd(resarr, olditemptr);
+ }
+ pfree(olditemsarr);
+ }
+}
+
+/*
+ * Remove all resources in array and call a callback function for each release.
+ */
+static void
+ResourceArrayRemoveAll(ResourceArray * resarr,
+ ResourceArrayRemoveCallback releasecb,
+ bool isCommit)
+{
+ while (resarr->nitems > 0)
+ {
+ releasecb(resarr->itemsarr, isCommit);
+ }
+}
+
+/*
+ * Return ResourceArray to initial state
+ */
+static void
+ResourceArrayFree(ResourceArray * resarr)
+{
+ Assert(resarr->nitems == 0);
+
+ resarr->capacity = 0;
+
+ if (!resarr->itemsarr)
+ return;
+
+ pfree(resarr->itemsarr);
+ resarr->itemsarr = NULL;
+}
+
+/*
* To speed up bulk releasing or reassigning locks from a resource owner to
* its parent, each resource owner has a small cache of locks it owns. The
* lock manager has the same information in its local lock hash table, and
@@ -56,53 +259,22 @@ typedef struct ResourceOwnerData
ResourceOwner nextchild; /* next child of same parent */
const char *name; /* name (just for debugging) */
- /* We have built-in support for remembering owned buffers */
- int nbuffers; /* number of owned buffer pins */
- Buffer *buffers; /* dynamically allocated array */
- int maxbuffers; /* currently allocated array size */
-
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
int nlocks; /* number of owned locks */
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
- /* We have built-in support for remembering catcache references */
- int ncatrefs; /* number of owned catcache pins */
- HeapTuple *catrefs; /* dynamically allocated array */
- int maxcatrefs; /* currently allocated array size */
-
- int ncatlistrefs; /* number of owned catcache-list pins */
- CatCList **catlistrefs; /* dynamically allocated array */
- int maxcatlistrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering relcache references */
- int nrelrefs; /* number of owned relcache pins */
- Relation *relrefs; /* dynamically allocated array */
- int maxrelrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering plancache references */
- int nplanrefs; /* number of owned plancache pins */
- CachedPlan **planrefs; /* dynamically allocated array */
- int maxplanrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering tupdesc references */
- int ntupdescs; /* number of owned tupdesc references */
- TupleDesc *tupdescs; /* dynamically allocated array */
- int maxtupdescs; /* currently allocated array size */
-
- /* We have built-in support for remembering snapshot references */
- int nsnapshots; /* number of owned snapshot references */
- Snapshot *snapshots; /* dynamically allocated array */
- int maxsnapshots; /* currently allocated array size */
-
- /* We have built-in support for remembering open temporary files */
- int nfiles; /* number of owned temporary files */
- File *files; /* dynamically allocated array */
- int maxfiles; /* currently allocated array size */
-
- /* We have built-in support for remembering dynamic shmem segments */
- int ndsms; /* number of owned shmem segments */
- dsm_segment **dsms; /* dynamically allocated array */
- int maxdsms; /* currently allocated array size */
+ /* We have built-in support for remembering: */
+
+ ResourceArray catrefarr; /* `HeapTuple`s */
+ ResourceArray catlistrefarr; /* `ResourceOwner`s */
+ ResourceArray relrefarr; /* `Relation`s */
+ ResourceArray planrefarr; /* `CachedPlan*`s */
+ ResourceArray tupdescarr; /* `TupleDesc`s */
+ ResourceArray snapshotarr; /* `Snapshot`s */
+ ResourceArray dsmarr; /* `dsm_segment*`s */
+ ResourceArray bufferarr; /* `Buffer`s */
+ ResourceArray filearr; /* `File`s */
+
} ResourceOwnerData;
@@ -168,6 +340,16 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
+ ResourceArrayInit(&(owner->catrefarr), sizeof(HeapTuple));
+ ResourceArrayInit(&(owner->catlistrefarr), sizeof(ResourceOwner));
+ ResourceArrayInit(&(owner->relrefarr), sizeof(Relation));
+ ResourceArrayInit(&(owner->planrefarr), sizeof(CachedPlan *));
+ ResourceArrayInit(&(owner->tupdescarr), sizeof(TupleDesc));
+ ResourceArrayInit(&(owner->snapshotarr), sizeof(Snapshot));
+ ResourceArrayInit(&(owner->dsmarr), sizeof(dsm_segment *));
+ ResourceArrayInit(&(owner->bufferarr), sizeof(Buffer));
+ ResourceArrayInit(&(owner->filearr), sizeof(File));
+
return owner;
}
@@ -221,6 +403,96 @@ ResourceOwnerRelease(ResourceOwner owner,
}
static void
+ReleaseCachedPlanCallback(const void *dataref, bool isCommit)
+{
+ CachedPlan *res = *((CachedPlan **) dataref);
+
+ if (isCommit)
+ PrintPlanCacheLeakWarning(res);
+ ReleaseCachedPlan(res, true);
+}
+
+static void
+ReleaseDSMCallback(const void *dataref, bool isCommit)
+{
+ dsm_segment *res = *((dsm_segment **) dataref);
+
+ if (isCommit)
+ PrintDSMLeakWarning(res);
+ dsm_detach(res);
+}
+
+static void
+ReleaseTupleDescCallback(const void *dataref, bool isCommit)
+{
+ TupleDesc res = *((TupleDesc *) dataref);
+
+ if (isCommit)
+ PrintTupleDescLeakWarning(res);
+ DecrTupleDescRefCount(res);
+}
+
+static void
+ReleaseSnapshotCallback(const void *dataref, bool isCommit)
+{
+ Snapshot res = *((Snapshot *) dataref);
+
+ if (isCommit)
+ PrintSnapshotLeakWarning(res);
+ UnregisterSnapshot(res);
+}
+
+static void
+ReleaseRelationCallback(const void *dataref, bool isCommit)
+{
+ Relation res = *((Relation *) dataref);
+
+ if (isCommit)
+ PrintRelCacheLeakWarning(res);
+ RelationClose(res);
+}
+
+static void
+ReleaseCatCacheListCallback(const void *dataref, bool isCommit)
+{
+ CatCList *res = *((CatCList **) dataref);
+
+ if (isCommit)
+ PrintCatCacheListLeakWarning(res);
+ ReleaseCatCacheList(res);
+}
+
+static void
+ReleaseCatCacheCallback(const void *dataref, bool isCommit)
+{
+ HeapTuple res = *((HeapTuple *) dataref);
+
+ if (isCommit)
+ PrintCatCacheLeakWarning(res);
+ ReleaseCatCache(res);
+}
+
+static void
+ReleaseBufferCallback(const void *dataref, bool isCommit)
+{
+ Buffer res = *((Buffer *) dataref);
+
+ if (isCommit)
+ PrintBufferLeakWarning(res);
+ ReleaseBuffer(res);
+}
+
+static void
+ReleaseFileCallback(const void *dataref, bool isCommit)
+{
+ File res = *((File *) dataref);
+
+ if (isCommit)
+ PrintFileLeakWarning(res);
+ FileClose(res);
+}
+
+static void
ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceReleasePhase phase,
bool isCommit,
@@ -252,46 +524,17 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* During a commit, there shouldn't be any remaining pins --- that
* would indicate failure to clean up the executor correctly --- so
* issue warnings. In the abort case, just clean up quietly.
- *
- * We are careful to do the releasing back-to-front, so as to avoid
- * O(N^2) behavior in ResourceOwnerForgetBuffer().
*/
- while (owner->nbuffers > 0)
- {
- if (isCommit)
- PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
- ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->bufferarr),
+ ReleaseBufferCallback, isCommit);
- /*
- * Release relcache references. Note that RelationClose will remove
- * the relref entry from my list, so I just have to iterate till there
- * are none.
- *
- * As with buffer pins, warn if any are left at commit time, and
- * release back-to-front for speed.
- */
- while (owner->nrelrefs > 0)
- {
- if (isCommit)
- PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]);
- RelationClose(owner->relrefs[owner->nrelrefs - 1]);
- }
+ /* Ditto for relcache references. */
+ ResourceArrayRemoveAll(&(owner->relrefarr),
+ ReleaseRelationCallback, isCommit);
- /*
- * Release dynamic shared memory segments. Note that dsm_detach()
- * will remove the segment from my list, so I just have to iterate
- * until there are none.
- *
- * As in the preceding cases, warn if there are leftover at commit
- * time.
- */
- while (owner->ndsms > 0)
- {
- if (isCommit)
- PrintDSMLeakWarning(owner->dsms[owner->ndsms - 1]);
- dsm_detach(owner->dsms[owner->ndsms - 1]);
- }
+ /* Ditto for dynamic shared memory segments */
+ ResourceArrayRemoveAll(&(owner->dsmarr),
+ ReleaseDSMCallback, isCommit);
}
else if (phase == RESOURCE_RELEASE_LOCKS)
{
@@ -351,48 +594,28 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
- while (owner->ncatrefs > 0)
- {
- if (isCommit)
- PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]);
- ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->catrefarr),
+ ReleaseCatCacheCallback, isCommit);
+
/* Ditto for catcache lists */
- while (owner->ncatlistrefs > 0)
- {
- if (isCommit)
- PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
- ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->catlistrefarr),
+ ReleaseCatCacheListCallback, isCommit);
+
/* Ditto for plancache references */
- while (owner->nplanrefs > 0)
- {
- if (isCommit)
- PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
- ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
- }
+ ResourceArrayRemoveAll(&(owner->planrefarr),
+ ReleaseCachedPlanCallback, isCommit);
+
/* Ditto for tupdesc references */
- while (owner->ntupdescs > 0)
- {
- if (isCommit)
- PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
- DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->tupdescarr),
+ ReleaseTupleDescCallback, isCommit);
+
/* Ditto for snapshot references */
- while (owner->nsnapshots > 0)
- {
- if (isCommit)
- PrintSnapshotLeakWarning(owner->snapshots[owner->nsnapshots - 1]);
- UnregisterSnapshot(owner->snapshots[owner->nsnapshots - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->snapshotarr),
+ ReleaseSnapshotCallback, isCommit);
/* Ditto for temporary files */
- while (owner->nfiles > 0)
- {
- if (isCommit)
- PrintFileLeakWarning(owner->files[owner->nfiles - 1]);
- FileClose(owner->files[owner->nfiles - 1]);
- }
+ ResourceArrayRemoveAll(&(owner->filearr),
+ ReleaseFileCallback, isCommit);
/* Clean up index scans too */
ReleaseResources_hash();
@@ -418,16 +641,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner != CurrentResourceOwner);
/* And it better not own any resources, either */
- Assert(owner->nbuffers == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
- Assert(owner->ncatrefs == 0);
- Assert(owner->ncatlistrefs == 0);
- Assert(owner->nrelrefs == 0);
- Assert(owner->ndsms == 0);
- Assert(owner->nplanrefs == 0);
- Assert(owner->ntupdescs == 0);
- Assert(owner->nsnapshots == 0);
- Assert(owner->nfiles == 0);
/*
* Delete children. The recursive call will delink the child from me, so
@@ -444,25 +658,15 @@ ResourceOwnerDelete(ResourceOwner owner)
ResourceOwnerNewParent(owner, NULL);
/* And free the object. */
- if (owner->buffers)
- pfree(owner->buffers);
- if (owner->catrefs)
- pfree(owner->catrefs);
- if (owner->catlistrefs)
- pfree(owner->catlistrefs);
- if (owner->relrefs)
- pfree(owner->relrefs);
- if (owner->planrefs)
- pfree(owner->planrefs);
- if (owner->tupdescs)
- pfree(owner->tupdescs);
- if (owner->snapshots)
- pfree(owner->snapshots);
- if (owner->files)
- pfree(owner->files);
- if (owner->dsms)
- pfree(owner->dsms);
-
+ ResourceArrayFree(&(owner->catrefarr));
+ ResourceArrayFree(&(owner->catlistrefarr));
+ ResourceArrayFree(&(owner->relrefarr));
+ ResourceArrayFree(&(owner->planrefarr));
+ ResourceArrayFree(&(owner->tupdescarr));
+ ResourceArrayFree(&(owner->snapshotarr));
+ ResourceArrayFree(&(owner->dsmarr));
+ ResourceArrayFree(&(owner->bufferarr));
+ ResourceArrayFree(&(owner->filearr));
pfree(owner);
}
@@ -575,26 +779,9 @@ UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
void
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
{
- int newmax;
-
- if (owner == NULL ||
- owner->nbuffers < owner->maxbuffers)
- return; /* nothing to do */
-
- if (owner->buffers == NULL)
- {
- newmax = 16;
- owner->buffers = (Buffer *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
- else
- {
- newmax = owner->maxbuffers * 2;
- owner->buffers = (Buffer *)
- repalloc(owner->buffers, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayEnlarge(&(owner->bufferarr));
}
/*
@@ -608,12 +795,9 @@ ResourceOwnerEnlargeBuffers(ResourceOwner owner)
void
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Assert(owner->nbuffers < owner->maxbuffers);
- owner->buffers[owner->nbuffers] = buffer;
- owner->nbuffers++;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayAdd(&(owner->bufferarr), (const void *) &buffer);
}
/*
@@ -625,33 +809,15 @@ ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Buffer *buffers = owner->buffers;
- int nb1 = owner->nbuffers - 1;
- int i;
+ bool res;
- /*
- * Scan back-to-front because it's more likely we are releasing a
- * recently pinned buffer. This isn't always the case of course, but
- * it's the way to bet.
- */
- for (i = nb1; i >= 0; i--)
- {
- if (buffers[i] == buffer)
- {
- while (i < nb1)
- {
- buffers[i] = buffers[i + 1];
- i++;
- }
- owner->nbuffers = nb1;
- return;
- }
- }
+ if (owner == NULL)
+ return;
+
+ res = ResourceArrayRemove(&(owner->bufferarr), (const void *) &buffer);
+ if (!res)
elog(ERROR, "buffer %d is not owned by resource owner %s",
buffer, owner->name);
- }
}
/*
@@ -667,6 +833,8 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
{
+ Assert(locallock != NULL);
+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have already overflowed */
@@ -714,25 +882,7 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
void
ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatrefs < owner->maxcatrefs)
- return; /* nothing to do */
-
- if (owner->catrefs == NULL)
- {
- newmax = 16;
- owner->catrefs = (HeapTuple *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatrefs * 2;
- owner->catrefs = (HeapTuple *)
- repalloc(owner->catrefs, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catrefarr));
}
/*
@@ -743,9 +893,7 @@ ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- Assert(owner->ncatrefs < owner->maxcatrefs);
- owner->catrefs[owner->ncatrefs] = tuple;
- owner->ncatrefs++;
+ ResourceArrayAdd(&(owner->catrefarr), (const void *) &tuple);
}
/*
@@ -754,25 +902,12 @@ ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- HeapTuple *catrefs = owner->catrefs;
- int nc1 = owner->ncatrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catrefarr),
+ (const void *) &tuple);
- for (i = nc1; i >= 0; i--)
- {
- if (catrefs[i] == tuple)
- {
- while (i < nc1)
- {
- catrefs[i] = catrefs[i + 1];
- i++;
- }
- owner->ncatrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache reference %p is not owned by resource owner %s",
- tuple, owner->name);
+ if (!res)
+ elog(ERROR, "catcache reference %p is not owned by resource owner %s",
+ tuple, owner->name);
}
/*
@@ -785,25 +920,7 @@ ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatlistrefs < owner->maxcatlistrefs)
- return; /* nothing to do */
-
- if (owner->catlistrefs == NULL)
- {
- newmax = 16;
- owner->catlistrefs = (CatCList **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatlistrefs * 2;
- owner->catlistrefs = (CatCList **)
- repalloc(owner->catlistrefs, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catlistrefarr));
}
/*
@@ -814,9 +931,7 @@ ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- Assert(owner->ncatlistrefs < owner->maxcatlistrefs);
- owner->catlistrefs[owner->ncatlistrefs] = list;
- owner->ncatlistrefs++;
+ ResourceArrayAdd(&(owner->catlistrefarr), (const void *) &list);
}
/*
@@ -825,25 +940,12 @@ ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- CatCList **catlistrefs = owner->catlistrefs;
- int nc1 = owner->ncatlistrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catlistrefarr),
+ (const void *) &list);
- for (i = nc1; i >= 0; i--)
- {
- if (catlistrefs[i] == list)
- {
- while (i < nc1)
- {
- catlistrefs[i] = catlistrefs[i + 1];
- i++;
- }
- owner->ncatlistrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
- list, owner->name);
+ if (!res)
+ elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
+ list, owner->name);
}
/*
@@ -856,25 +958,7 @@ ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nrelrefs < owner->maxrelrefs)
- return; /* nothing to do */
-
- if (owner->relrefs == NULL)
- {
- newmax = 16;
- owner->relrefs = (Relation *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
- else
- {
- newmax = owner->maxrelrefs * 2;
- owner->relrefs = (Relation *)
- repalloc(owner->relrefs, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->relrefarr));
}
/*
@@ -885,9 +969,7 @@ ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
- Assert(owner->nrelrefs < owner->maxrelrefs);
- owner->relrefs[owner->nrelrefs] = rel;
- owner->nrelrefs++;
+ ResourceArrayAdd(&(owner->relrefarr), (const void *) &rel);
}
/*
@@ -896,25 +978,12 @@ ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
- Relation *relrefs = owner->relrefs;
- int nr1 = owner->nrelrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->relrefarr),
+ (const void *) &rel);
- for (i = nr1; i >= 0; i--)
- {
- if (relrefs[i] == rel)
- {
- while (i < nr1)
- {
- relrefs[i] = relrefs[i + 1];
- i++;
- }
- owner->nrelrefs = nr1;
- return;
- }
- }
- elog(ERROR, "relcache reference %s is not owned by resource owner %s",
- RelationGetRelationName(rel), owner->name);
+ if (!res)
+ elog(ERROR, "relcache reference %s is not owned by resource owner %s",
+ RelationGetRelationName(rel), owner->name);
}
/*
@@ -937,25 +1006,7 @@ PrintRelCacheLeakWarning(Relation rel)
void
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nplanrefs < owner->maxplanrefs)
- return; /* nothing to do */
-
- if (owner->planrefs == NULL)
- {
- newmax = 16;
- owner->planrefs = (CachedPlan **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
- else
- {
- newmax = owner->maxplanrefs * 2;
- owner->planrefs = (CachedPlan **)
- repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->planrefarr));
}
/*
@@ -966,9 +1017,7 @@ ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- Assert(owner->nplanrefs < owner->maxplanrefs);
- owner->planrefs[owner->nplanrefs] = plan;
- owner->nplanrefs++;
+ ResourceArrayAdd(&(owner->planrefarr), (const void *) &plan);
}
/*
@@ -977,25 +1026,12 @@ ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
void
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- CachedPlan **planrefs = owner->planrefs;
- int np1 = owner->nplanrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->planrefarr),
+ (const void *) &plan);
- for (i = np1; i >= 0; i--)
- {
- if (planrefs[i] == plan)
- {
- while (i < np1)
- {
- planrefs[i] = planrefs[i + 1];
- i++;
- }
- owner->nplanrefs = np1;
- return;
- }
- }
- elog(ERROR, "plancache reference %p is not owned by resource owner %s",
- plan, owner->name);
+ if (!res)
+ elog(ERROR, "plancache reference %p is not owned by resource owner %s",
+ plan, owner->name);
}
/*
@@ -1017,25 +1053,7 @@ PrintPlanCacheLeakWarning(CachedPlan *plan)
void
ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ntupdescs < owner->maxtupdescs)
- return; /* nothing to do */
-
- if (owner->tupdescs == NULL)
- {
- newmax = 16;
- owner->tupdescs = (TupleDesc *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
- else
- {
- newmax = owner->maxtupdescs * 2;
- owner->tupdescs = (TupleDesc *)
- repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->tupdescarr));
}
/*
@@ -1046,9 +1064,7 @@ ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
void
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- Assert(owner->ntupdescs < owner->maxtupdescs);
- owner->tupdescs[owner->ntupdescs] = tupdesc;
- owner->ntupdescs++;
+ ResourceArrayAdd(&(owner->tupdescarr), (const void *) &tupdesc);
}
/*
@@ -1057,25 +1073,12 @@ ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
void
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- TupleDesc *tupdescs = owner->tupdescs;
- int nt1 = owner->ntupdescs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->tupdescarr),
+ (const void *) &tupdesc);
- for (i = nt1; i >= 0; i--)
- {
- if (tupdescs[i] == tupdesc)
- {
- while (i < nt1)
- {
- tupdescs[i] = tupdescs[i + 1];
- i++;
- }
- owner->ntupdescs = nt1;
- return;
- }
- }
- elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
- tupdesc, owner->name);
+ if (!res)
+ elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
+ tupdesc, owner->name);
}
/*
@@ -1099,25 +1102,7 @@ PrintTupleDescLeakWarning(TupleDesc tupdesc)
void
ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nsnapshots < owner->maxsnapshots)
- return; /* nothing to do */
-
- if (owner->snapshots == NULL)
- {
- newmax = 16;
- owner->snapshots = (Snapshot *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
- else
- {
- newmax = owner->maxsnapshots * 2;
- owner->snapshots = (Snapshot *)
- repalloc(owner->snapshots, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
+ ResourceArrayEnlarge(&(owner->snapshotarr));
}
/*
@@ -1128,9 +1113,7 @@ ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
void
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Assert(owner->nsnapshots < owner->maxsnapshots);
- owner->snapshots[owner->nsnapshots] = snapshot;
- owner->nsnapshots++;
+ ResourceArrayAdd(&(owner->snapshotarr), (const void *) &snapshot);
}
/*
@@ -1139,25 +1122,12 @@ ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
void
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Snapshot *snapshots = owner->snapshots;
- int ns1 = owner->nsnapshots - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->snapshotarr),
+ (const void *) &snapshot);
- for (i = ns1; i >= 0; i--)
- {
- if (snapshots[i] == snapshot)
- {
- while (i < ns1)
- {
- snapshots[i] = snapshots[i + 1];
- i++;
- }
- owner->nsnapshots = ns1;
- return;
- }
- }
- elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
- snapshot, owner->name);
+ if (!res)
+ elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
+ snapshot, owner->name);
}
/*
@@ -1182,25 +1152,7 @@ PrintSnapshotLeakWarning(Snapshot snapshot)
void
ResourceOwnerEnlargeFiles(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nfiles < owner->maxfiles)
- return; /* nothing to do */
-
- if (owner->files == NULL)
- {
- newmax = 16;
- owner->files = (File *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
- else
- {
- newmax = owner->maxfiles * 2;
- owner->files = (File *)
- repalloc(owner->files, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
+ ResourceArrayEnlarge(&(owner->filearr));
}
/*
@@ -1211,9 +1163,7 @@ ResourceOwnerEnlargeFiles(ResourceOwner owner)
void
ResourceOwnerRememberFile(ResourceOwner owner, File file)
{
- Assert(owner->nfiles < owner->maxfiles);
- owner->files[owner->nfiles] = file;
- owner->nfiles++;
+ ResourceArrayAdd(&(owner->filearr), (const void *) &file);
}
/*
@@ -1222,25 +1172,12 @@ ResourceOwnerRememberFile(ResourceOwner owner, File file)
void
ResourceOwnerForgetFile(ResourceOwner owner, File file)
{
- File *files = owner->files;
- int ns1 = owner->nfiles - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->filearr),
+ (const void *) &file);
- for (i = ns1; i >= 0; i--)
- {
- if (files[i] == file)
- {
- while (i < ns1)
- {
- files[i] = files[i + 1];
- i++;
- }
- owner->nfiles = ns1;
- return;
- }
- }
- elog(ERROR, "temporery file %d is not owned by resource owner %s",
- file, owner->name);
+ if (!res)
+ elog(ERROR, "temporery file %d is not owned by resource owner %s",
+ file, owner->name);
}
@@ -1265,26 +1202,7 @@ PrintFileLeakWarning(File file)
void
ResourceOwnerEnlargeDSMs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ndsms < owner->maxdsms)
- return; /* nothing to do */
-
- if (owner->dsms == NULL)
- {
- newmax = 16;
- owner->dsms = (dsm_segment **)
- MemoryContextAlloc(TopMemoryContext,
- newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
- else
- {
- newmax = owner->maxdsms * 2;
- owner->dsms = (dsm_segment **)
- repalloc(owner->dsms, newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
+ ResourceArrayEnlarge(&(owner->dsmarr));
}
/*
@@ -1295,9 +1213,7 @@ ResourceOwnerEnlargeDSMs(ResourceOwner owner)
void
ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
{
- Assert(owner->ndsms < owner->maxdsms);
- owner->dsms[owner->ndsms] = seg;
- owner->ndsms++;
+ ResourceArrayAdd(&(owner->dsmarr), (const void *) &seg);
}
/*
@@ -1306,26 +1222,12 @@ ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
void
ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
{
- dsm_segment **dsms = owner->dsms;
- int ns1 = owner->ndsms - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->dsmarr),
+ (const void *) &seg);
- for (i = ns1; i >= 0; i--)
- {
- if (dsms[i] == seg)
- {
- while (i < ns1)
- {
- dsms[i] = dsms[i + 1];
- i++;
- }
- owner->ndsms = ns1;
- return;
- }
- }
- elog(ERROR,
- "dynamic shared memory segment %u is not owned by resource owner %s",
- dsm_segment_handle(seg), owner->name);
+ if (!res)
+ elog(ERROR, "dynamic shared memory segment %u is not owned by resource"
+ " owner %s", dsm_segment_handle(seg), owner->name);
}
resource-owner-optimization-v3-step2.patchtext/x-patchDownload
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 39324fe..d2b37d5 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -37,6 +37,27 @@
* which should be called before corresponding ResourceOwnerRemember* calls
* (see below). Internally each type of resource is stored in separate
* ResourceArray.
+ *
+ * There are two major reasons for using ResourceArray instead of, say,
+ * regular C arrays.
+ *
+ * Firstly we would like to prevent code duplication. For instance
+ * ResourceArray provides generic Remember/Forget/Enlarge procedures, so
+ * corresponding ResourceOwner* procedures are just a typesafe wrappers for
+ * these procedures. Note that different resources have different sizeof. Thus
+ * ResourceArray should know size of resource and store all data in char[]
+ * buffer.
+ *
+ * Secondly ResourceArray must be more efficient than regular C array.
+ * Previous implementation of ResourceOwner used arrays. It had O(1) complexity
+ * of Remember procedures and O(N) complexity of Forget procedures where N is a
+ * number of remembered resources. It turned out that such implementation
+ * creates a bottleneck in some cases, e.g. when working with partitioned
+ * tables which have a lot of (say, thousands) partitions. New implementation
+ * in general could be considered a hash table with some specific optimizations.
+ * It has O(1) complexity of both Remember and Forget procedures and
+ * apparently doesn't cause any performance degradation compared to previous
+ * implementation.
*/
typedef struct ResourceArray
{
@@ -44,21 +65,60 @@ typedef struct ResourceArray
uint32 itemsizelg:2; /* sizeof one item log 2 */
uint32 capacity:30; /* capacity of array */
uint32 nitems; /* how many items is stored in items array */
+ uint32 maxitems; /* precalculated RESARRAY_MAX_ITEMS(capacity) */
} ResourceArray;
/*
* This number is used as initial size of resource array. If given number of
* items is not enough, we double array size and reallocate memory.
+ *
+ * Should be power of two since we use (arrsize -1) as mask for hash value.
+ *
*/
#define RESARRAY_INIT_SIZE 16
/*
+ * How many items could be stored in a resource array of given capacity. If
+ * this number is reached we need to resize an array to prevent hash collisions.
+ *
+ * This computation actually costs only two additions and one binary shift.
+ */
+#define RESARRAY_MAX_ITEMS(capacity) ((capacity)*3/4)
+
+/*
* Such type of callback function is called when resource stored in
* ResourceArray is released using ResourceArrayFree. Callback should
* _actually_ release a resource so nitems value will be decremented.
*/
typedef void (*ResourceArrayRemoveCallback) (const void *ref, bool isCommit);
+/* Used as argument to memcmp to determine if ResourceArray[i] is free. */
+static const char RESOURCE_ARRAY_ZERO_ELEMENT[sizeof(void *)] =
+{
+ 0
+};
+
+/*
+ * Calculate hash_any of given data. For uint32 values use faster hash_uint32.
+ */
+static Datum
+ResourceArrayHash(const void *data, int size)
+{
+ uint32 tmp;
+
+ Assert(size == sizeof(uint32) || size == sizeof(void *));
+
+ if (size == sizeof(uint32))
+ {
+ tmp = *((const uint32 *) data);
+ return hash_uint32(tmp);
+ }
+ else
+ {
+ return hash_any(data, size);
+ }
+}
+
/*
* Initialize ResourceArray
*/
@@ -69,6 +129,7 @@ ResourceArrayInit(ResourceArray * resarr, uint32 itemsize)
Assert(resarr->itemsarr == NULL);
Assert(resarr->capacity == 0);
Assert(resarr->nitems == 0);
+ Assert(resarr->maxitems == 0);
resarr->itemsizelg = 0;
while (itemsize > 1)
@@ -89,22 +150,42 @@ static void
ResourceArrayAdd(ResourceArray * resarr, const void *dataref)
{
char *itemptr;
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
uint32 itemsize = 1 << resarr->itemsizelg;
+ Assert(memcmp(dataref, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0);
+ Assert(resarr->maxitems > resarr->nitems);
Assert(resarr->capacity > 0);
Assert(resarr->itemsarr != NULL);
- Assert(resarr->nitems < resarr->capacity);
/*
- * Read next line as:
- *
- * itemptr = &(itemsarr[resarr->nitems])
- *
- * We use binary shift since compiler doesn't know that itemsize is always
- * power of two. It would use multiplication instead of efficient binary
- * shift in code `resarr->nitems * itemsize`.
+ * Hashing is quite expensive, so we use it only for large arrays. For
+ * small arrays we just use a linear scan.
*/
- itemptr = resarr->itemsarr + (resarr->nitems << resarr->itemsizelg);
+ if (resarr->capacity == RESARRAY_INIT_SIZE)
+ idx = resarr->nitems;
+ else
+ idx = ResourceArrayHash(dataref, itemsize);
+ idx &= mask;
+
+ while (true)
+ {
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is
+ * always power of two. It would use multiplication instead of
+ * efficient binary shift in code `idx * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if (memcmp(itemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) == 0)
+ break;
+ idx = (idx + 1) & mask;
+ }
+
memcpy(itemptr, dataref, itemsize);
resarr->nitems++;
}
@@ -117,17 +198,27 @@ ResourceArrayAdd(ResourceArray * resarr, const void *dataref)
static bool
ResourceArrayRemove(ResourceArray * resarr, const void *dataref)
{
- Datum idx;
char *itemptr;
- char *lastitemptr;
+ Datum idx;
+ uint32 i;
+ Datum mask = resarr->capacity - 1;
uint32 itemsize = 1 << resarr->itemsizelg;
+ Assert(memcmp(dataref, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0);
Assert(resarr->capacity > 0);
Assert(resarr->itemsarr != NULL);
- lastitemptr = resarr->itemsarr +
- ((resarr->nitems - 1) << resarr->itemsizelg);
- for (idx = 0; idx < resarr->nitems; idx++)
+ /*
+ * Hashing is quite expensive, so we use it only for large arrays. For
+ * small arrays we use a linear scan.
+ */
+ if (resarr->capacity == RESARRAY_INIT_SIZE)
+ idx = 0;
+ else
+ idx = ResourceArrayHash(dataref, itemsize);
+ idx &= mask;
+
+ for (i = 0; i < resarr->capacity; i++)
{
/*
* Read next line as:
@@ -141,10 +232,11 @@ ResourceArrayRemove(ResourceArray * resarr, const void *dataref)
itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
if (memcmp(itemptr, dataref, itemsize) == 0)
{
- memcpy(itemptr, lastitemptr, itemsize);
+ memset(itemptr, 0, itemsize);
resarr->nitems--;
return true;
}
+ idx = (idx + 1) & mask;
}
return false;
@@ -166,7 +258,7 @@ ResourceArrayEnlarge(ResourceArray * resarr)
Assert(resarr->itemsizelg != 0);
- if (resarr->nitems < resarr->capacity)
+ if (resarr->nitems < resarr->maxitems)
return; /* nothing to do */
olditemsarr = resarr->itemsarr;
@@ -176,6 +268,7 @@ ResourceArrayEnlarge(ResourceArray * resarr)
resarr->itemsarr = (char *)
MemoryContextAllocZero(TopMemoryContext,
resarr->capacity * itemsize);
+ resarr->maxitems = RESARRAY_MAX_ITEMS(resarr->capacity);
resarr->nitems = 0;
if (olditemsarr != NULL)
@@ -194,7 +287,8 @@ ResourceArrayEnlarge(ResourceArray * resarr)
* efficient binary shift in code `oldcap * itemsize`.
*/
olditemptr = olditemsarr + (oldcap << resarr->itemsizelg);
- ResourceArrayAdd(resarr, olditemptr);
+ if (memcmp(olditemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) != 0)
+ ResourceArrayAdd(resarr, olditemptr);
}
pfree(olditemsarr);
}
@@ -208,9 +302,28 @@ ResourceArrayRemoveAll(ResourceArray * resarr,
ResourceArrayRemoveCallback releasecb,
bool isCommit)
{
+ uint32 idx = 0;
+ char *itemptr;
+ uint32 itemsize = 1 << resarr->itemsizelg;
+
while (resarr->nitems > 0)
{
- releasecb(resarr->itemsarr, isCommit);
+ /*
+ * Read next line as:
+ *
+ * itemptr = &(itemsarr[idx])
+ *
+ * We use binary shift since compiler doesn't know that itemsize is
+ * always power of two. It would use multiplication instead of
+ * efficient binary shift in code `idx * itemsize`.
+ */
+ itemptr = resarr->itemsarr + (idx << resarr->itemsizelg);
+ if (memcmp(itemptr, RESOURCE_ARRAY_ZERO_ELEMENT, itemsize) == 0)
+ {
+ idx++;
+ continue;
+ }
+ releasecb(itemptr, isCommit);
}
}
@@ -223,6 +336,7 @@ ResourceArrayFree(ResourceArray * resarr)
Assert(resarr->nitems == 0);
resarr->capacity = 0;
+ resarr->maxitems = 0;
if (!resarr->itemsarr)
return;
On Mon, Dec 14, 2015 at 6:47 AM, Aleksander Alekseev
<a.alekseev@postgrespro.ru> wrote:
Here is my fix for item 4.
I don't know, I'm still not very comfortable with this. And Tom
didn't like dictating that hash_any() must be no-fail, though I'm not
sure why.
Let's wait to see what others think. I kind of hope there's a way of
getting the benefits we want here without so much code churn.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
I don't know, I'm still not very comfortable with this. And Tom
didn't like dictating that hash_any() must be no-fail, though I'm not
sure why.
What I definitely didn't like was assuming at a distance that it would
be no-fail. If we're to depend on that, the patch had better attach
a comment saying so to the header comments of the function(s) it's
assuming that about. Otherwise, somebody could hack up hashfunc.c
in a way that breaks the assumption, without any clue that some code
in a very-far-away module is critically reliant on it.
Let's wait to see what others think.
A few observations:
* This bit is too cute by half, if not three-quarters:
+ uint32 itemsizelg:2; /* sizeof one item log 2 */
+ uint32 capacity:30; /* capacity of array */
Is there a good reason to assume that the only things we'll ever store
in these arrays are of size no more than 8 bytes? Are we so desperate
to save space that we cannot spare two separate words for itemsize and
capacity? (ISTM it's a good bet that the extra code for accessing these
bitfields occupies more space than would be saved, considering how few
ResourceOwners typically exist at one time.) Let's just make it a couple
of ints and be done. Actually, maybe nitems and capacity should be
size_t, just in case.
* An alternative design would be to forget itemsizelg altogether and insist
that everything stored in the resource arrays be a Datum, which could then
be coerced to/from some form of integer or some form of pointer as
appropriate. That would waste some space in the int case, but it would
considerably simplify both the ResourceArray code and the APIs to it,
which might be worth the price of assuming we'll never store anything
bigger than 8 bytes. It also would make this look more like some
existing APIs such as the on_exit callbacks.
* A lot of the code churn comes from the insistence on defining callbacks,
which I'm dubious that we need. We could instead have a function that is
"get any convenient one of the array elements" and revise the loops in
ResourceOwnerReleaseInternal to be like
while ((item = getconvenientitem(resourcearray)))
{
drop item in exactly the same way as before
}
I find that preferable to the proposed ResourceArrayRemoveAll
+ while (resarr->nitems > 0)
+ {
+ releasecb(resarr->itemsarr, isCommit);
+ }
which certainly looks like it's an infinite loop; it's assuming (again
with no documentation) that the callback function will cause the array
to get smaller somehow. With the existing coding, it's much more clear
why we think the loops will terminate.
* The reason that ResourceOwnerReleaseInternal was not horribly
inefficient was that its notion of "any convenient one" of the items
to be deleted next was in fact the one that the corresponding Forget
function would examine first, thus avoiding an O(N^2) cost to
re-identify the item to be dropped. I think we should make an effort
to be more explicit about that connection in any rewrite. In particular,
it looks to me like when a hash array is in use, things will get slower
not faster because we'll be adding a hash lookup step to each forget
operation. Maybe we should consider adjusting the APIs so that that
can be avoided. Or possibly we could have internal state in the
ResourceArrays that says "we expect this item to be dropped in a moment,
check that before going to the trouble of a hash lookup".
* Actually, I'm not convinced that the proposed reimplementation of
ResourceArrayRemove isn't horribly slow much of the time. It sure
looks like it could degrade to a linear search very easily.
* I still say that the assumption embodied as RESOURCE_ARRAY_ZERO_ELEMENT
(ie that no valid entry is all-zero-bits) is pretty unacceptable. It
might work for pointers, but I don't like it for resources represented
by integer indexes.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I believe I fixed all flaws mentioned so far (see attachment).
Also I did a new benchmark to make sure that new patch makes PostgreSQL
work faster and doesn't cause performance degradation in some cases.
"usual pgbench" row corresponds to `pgbench -j 8 -c 8 -T 30 pgbench`
performed on a 4-core PC.
"N partitions" rows correspond to a benchmark described in a first
message of this thread performed on the same PC. N is and argument
given to gen.pl script i.e. number of partitions in generated
partitioned table.
Here are results (3 tests, TPS excluding connections establishing):
Test | Before | After | Delta avg
----------------|-----------|-----------|-------------
| 295.7 | 295.0 |
usual pgbench | 303.1 | 299.6 | ~ 0%
| 297.7 | 302.7 |
----------------|-----------|-----------|-------------
| 28022.3 | 27956.1 |
10 partitions | 27550.1 | 28916.9 | ~ 0%
| 28617.0 | 28022.9 |
----------------|-----------|-----------|-------------
| 3021.4 | 3184.0 |
100 partitions | 2949.1 | 3120.1 | 3% more TPS
| 2870.6 | 2825.2 |
----------------|-----------|-----------|-------------
| 106.7 | 158.6 |
1000 partitions | 105.2 | 168.4 | 53% more TPS
| 105.9 | 162.0 |
On Fri, 18 Dec 2015 13:39:01 -0500
Tom Lane <tgl@sss.pgh.pa.us> wrote:
Show quoted text
Robert Haas <robertmhaas@gmail.com> writes:
I don't know, I'm still not very comfortable with this. And Tom
didn't like dictating that hash_any() must be no-fail, though I'm
not sure why.What I definitely didn't like was assuming at a distance that it would
be no-fail. If we're to depend on that, the patch had better attach
a comment saying so to the header comments of the function(s) it's
assuming that about. Otherwise, somebody could hack up hashfunc.c
in a way that breaks the assumption, without any clue that some code
in a very-far-away module is critically reliant on it.Let's wait to see what others think.
A few observations:
* This bit is too cute by half, if not three-quarters:
+ uint32 itemsizelg:2; /* sizeof one
item log 2 */
+ uint32 capacity:30; /* capacity of
array */Is there a good reason to assume that the only things we'll ever store
in these arrays are of size no more than 8 bytes? Are we so desperate
to save space that we cannot spare two separate words for itemsize and
capacity? (ISTM it's a good bet that the extra code for accessing
these bitfields occupies more space than would be saved, considering
how few ResourceOwners typically exist at one time.) Let's just make
it a couple of ints and be done. Actually, maybe nitems and capacity
should be size_t, just in case.* An alternative design would be to forget itemsizelg altogether and
insist that everything stored in the resource arrays be a Datum,
which could then be coerced to/from some form of integer or some form
of pointer as appropriate. That would waste some space in the int
case, but it would considerably simplify both the ResourceArray code
and the APIs to it, which might be worth the price of assuming we'll
never store anything bigger than 8 bytes. It also would make this
look more like some existing APIs such as the on_exit callbacks.* A lot of the code churn comes from the insistence on defining
callbacks, which I'm dubious that we need. We could instead have a
function that is "get any convenient one of the array elements" and
revise the loops in ResourceOwnerReleaseInternal to be likewhile ((item = getconvenientitem(resourcearray)))
{
drop item in exactly the same way as before
}I find that preferable to the proposed ResourceArrayRemoveAll
+ while (resarr->nitems > 0) + { + releasecb(resarr->itemsarr, isCommit); + }which certainly looks like it's an infinite loop; it's assuming (again
with no documentation) that the callback function will cause the array
to get smaller somehow. With the existing coding, it's much more
clear why we think the loops will terminate.* The reason that ResourceOwnerReleaseInternal was not horribly
inefficient was that its notion of "any convenient one" of the items
to be deleted next was in fact the one that the corresponding Forget
function would examine first, thus avoiding an O(N^2) cost to
re-identify the item to be dropped. I think we should make an effort
to be more explicit about that connection in any rewrite. In
particular, it looks to me like when a hash array is in use, things
will get slower not faster because we'll be adding a hash lookup step
to each forget operation. Maybe we should consider adjusting the
APIs so that that can be avoided. Or possibly we could have internal
state in the ResourceArrays that says "we expect this item to be
dropped in a moment, check that before going to the trouble of a hash
lookup".* Actually, I'm not convinced that the proposed reimplementation of
ResourceArrayRemove isn't horribly slow much of the time. It sure
looks like it could degrade to a linear search very easily.* I still say that the assumption embodied as
RESOURCE_ARRAY_ZERO_ELEMENT (ie that no valid entry is all-zero-bits)
is pretty unacceptable. It might work for pointers, but I don't like
it for resources represented by integer indexes.regards, tom lane
Attachments:
resource-owner-optimization-v4-step1.patchtext/x-patchDownload
diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c
index 9ee654e..5a00a03 100644
--- a/src/backend/access/hash/hashfunc.c
+++ b/src/backend/access/hash/hashfunc.c
@@ -297,6 +297,9 @@ hashvarlena(PG_FUNCTION_ARGS)
* of 2. There is no need to do mod a prime (mod is sooo slow!).
* If you need less than 32 bits, use a bitmask.
*
+ * This procedure never fails. Its important. Some code notably ResourceOwner
+ * relies on this.
+ *
* Note: we could easily change this function to return a 64-bit hash value
* by using the final values of both b and c. b is perhaps a little less
* well mixed than c, however.
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 0e7acbf..abed36c 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -29,6 +29,219 @@
#include "utils/snapmgr.h"
/*
+ * ResourceArray is a common structure for storing different types of resources.
+ *
+ * ResourceOwner can own `HeapTuple`s, `Relation`s, `Snapshot`s, etc. For
+ * each resource type there are procedures ResourceOwnerRemember* and
+ * ResourceOwnerForget*. There are also ResourceOwnerEnlarge* procedures
+ * which should be called before corresponding ResourceOwnerRemember* calls
+ * (see below). Internally each type of resource is stored in separate
+ * ResourceArray.
+ *
+ * There are two major reasons for using ResourceArray instead of, say,
+ * regular C arrays.
+ *
+ * Firstly we would like to prevent code duplication. For instance
+ * ResourceArray provides generic Remember/Forget/Enlarge procedures, so
+ * corresponding ResourceOwner* procedures are just a typesafe wrappers for
+ * these procedures.
+ *
+ * Secondly ResourceArray must be more efficient than regular C array.
+ * Current implementation in general could be considered a hash table. It has
+ * O(1) complexity of both Remember and Forget procedures.
+ */
+typedef struct ResourceArray
+{
+ Datum *itemsarr; /* buffer for storing values */
+ Datum invalidval; /* value that is considered invalid */
+ uint32 capacity; /* capacity of array */
+ uint32 nitems; /* how many items is stored in items array */
+ uint32 maxitems; /* precalculated RESARRAY_MAX_ITEMS(capacity) */
+ uint32 lastidx; /* index of last item returned by GetAny */
+} ResourceArray;
+
+/*
+ * This number is used as initial size of resource array. If given number of
+ * items is not enough, we double array size and reallocate memory.
+ *
+ * Should be power of two since we use (arrsize - 1) as mask for hash value.
+ *
+ */
+#define RESARRAY_INIT_SIZE 16
+
+/*
+ * How many items could be stored in a resource array of given capacity. If
+ * this number is reached we need to resize an array to prevent hash collisions.
+ *
+ * This computation actually costs only two additions and one binary shift.
+ */
+#define RESARRAY_MAX_ITEMS(capacity) ((capacity)*3/4)
+
+/*
+ * Initialize ResourceArray
+ */
+static void
+ResourceArrayInit(ResourceArray * resarr, Datum invalidval)
+{
+ Assert(resarr->itemsarr == NULL);
+ Assert(resarr->capacity == 0);
+ Assert(resarr->nitems == 0);
+ Assert(resarr->maxitems == 0);
+ Assert(resarr->invalidval == 0);
+ Assert(resarr->lastidx == 0);
+
+ resarr->invalidval = invalidval;
+}
+
+/*
+ * Add a resource to ResourceArray
+ *
+ * Caller must have previously done ResourceArrayEnlarge()
+ */
+static void
+ResourceArrayAdd(ResourceArray * resarr, Datum data)
+{
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
+
+ Assert(resarr->maxitems > resarr->nitems);
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+ Assert(data != resarr->invalidval);
+
+ idx = hash_any((void *) &data, sizeof(data)) & mask;
+
+ while (true)
+ {
+ if (resarr->itemsarr[idx] == resarr->invalidval)
+ break;
+ idx = (idx + 1) & mask;
+ }
+
+ resarr->itemsarr[idx] = data;
+ resarr->nitems++;
+}
+
+/*
+ * Remove a resource from ResourceArray
+ *
+ * Returns true on success, false if resource was not found
+ */
+static bool
+ResourceArrayRemove(ResourceArray * resarr, Datum data)
+{
+ uint32 i;
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
+
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+ Assert(data != resarr->invalidval);
+
+ idx = hash_any((void *) &data, sizeof(data)) & mask;
+ for (i = 0; i < resarr->capacity; i++)
+ {
+ if (resarr->itemsarr[idx] == data)
+ {
+ resarr->itemsarr[idx] = resarr->invalidval;
+ resarr->nitems--;
+ return true;
+ }
+ idx = (idx + 1) & mask;
+ }
+
+ return false;
+}
+
+/*
+ * Make sure there is a room for at least one more resource in an array.
+ *
+ * This is separate from actually inserting a resource because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+static void
+ResourceArrayEnlarge(ResourceArray * resarr)
+{
+ uint32 i,
+ oldcap;
+ Datum *olditemsarr;
+
+ if (resarr->nitems < resarr->maxitems)
+ return; /* nothing to do */
+
+ olditemsarr = resarr->itemsarr;
+ oldcap = resarr->capacity;
+
+ resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
+ resarr->itemsarr = (Datum *)
+ MemoryContextAlloc(TopMemoryContext,
+ resarr->capacity * sizeof(Datum));
+ resarr->maxitems = RESARRAY_MAX_ITEMS(resarr->capacity);
+ resarr->nitems = 0;
+
+ for (i = 0; i < resarr->capacity; i++)
+ resarr->itemsarr[i] = resarr->invalidval;
+
+ if (olditemsarr != NULL)
+ {
+ while (oldcap > 0)
+ {
+ oldcap--;
+ if (olditemsarr[oldcap] != resarr->invalidval)
+ ResourceArrayAdd(resarr, olditemsarr[oldcap]);
+ }
+ pfree(olditemsarr);
+ }
+}
+
+/*
+ * Get any convenient element.
+ *
+ * Returns true on success, false on failure.
+ */
+static bool
+ResourceArrayGetAny(ResourceArray * resarr, Datum *out)
+{
+ uint32 mask;
+
+ if (resarr->nitems == 0)
+ return false;
+
+ Assert(resarr->capacity > 0);
+ mask = resarr->capacity - 1;
+
+ for (;;)
+ {
+ resarr->lastidx = resarr->lastidx & mask;
+ if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
+ break;
+
+ resarr->lastidx++;
+ }
+
+ *out = resarr->itemsarr[resarr->lastidx];
+ return true;
+}
+
+/*
+ * Return ResourceArray to initial state
+ */
+static void
+ResourceArrayFree(ResourceArray * resarr)
+{
+ Assert(resarr->nitems == 0);
+
+ resarr->capacity = 0;
+ resarr->maxitems = 0;
+
+ if (!resarr->itemsarr)
+ return;
+
+ pfree(resarr->itemsarr);
+ resarr->itemsarr = NULL;
+}
+
+/*
* To speed up bulk releasing or reassigning locks from a resource owner to
* its parent, each resource owner has a small cache of locks it owns. The
* lock manager has the same information in its local lock hash table, and
@@ -56,53 +269,22 @@ typedef struct ResourceOwnerData
ResourceOwner nextchild; /* next child of same parent */
const char *name; /* name (just for debugging) */
- /* We have built-in support for remembering owned buffers */
- int nbuffers; /* number of owned buffer pins */
- Buffer *buffers; /* dynamically allocated array */
- int maxbuffers; /* currently allocated array size */
-
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
int nlocks; /* number of owned locks */
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
- /* We have built-in support for remembering catcache references */
- int ncatrefs; /* number of owned catcache pins */
- HeapTuple *catrefs; /* dynamically allocated array */
- int maxcatrefs; /* currently allocated array size */
-
- int ncatlistrefs; /* number of owned catcache-list pins */
- CatCList **catlistrefs; /* dynamically allocated array */
- int maxcatlistrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering relcache references */
- int nrelrefs; /* number of owned relcache pins */
- Relation *relrefs; /* dynamically allocated array */
- int maxrelrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering plancache references */
- int nplanrefs; /* number of owned plancache pins */
- CachedPlan **planrefs; /* dynamically allocated array */
- int maxplanrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering tupdesc references */
- int ntupdescs; /* number of owned tupdesc references */
- TupleDesc *tupdescs; /* dynamically allocated array */
- int maxtupdescs; /* currently allocated array size */
-
- /* We have built-in support for remembering snapshot references */
- int nsnapshots; /* number of owned snapshot references */
- Snapshot *snapshots; /* dynamically allocated array */
- int maxsnapshots; /* currently allocated array size */
-
- /* We have built-in support for remembering open temporary files */
- int nfiles; /* number of owned temporary files */
- File *files; /* dynamically allocated array */
- int maxfiles; /* currently allocated array size */
-
- /* We have built-in support for remembering dynamic shmem segments */
- int ndsms; /* number of owned shmem segments */
- dsm_segment **dsms; /* dynamically allocated array */
- int maxdsms; /* currently allocated array size */
+ /* We have built-in support for remembering: */
+
+ ResourceArray catrefarr; /* `HeapTuple`s */
+ ResourceArray catlistrefarr; /* `ResourceOwner`s */
+ ResourceArray relrefarr; /* `Relation`s */
+ ResourceArray planrefarr; /* `CachedPlan*`s */
+ ResourceArray tupdescarr; /* `TupleDesc`s */
+ ResourceArray snapshotarr; /* `Snapshot`s */
+ ResourceArray dsmarr; /* `dsm_segment*`s */
+ ResourceArray bufferarr; /* `Buffer`s */
+ ResourceArray filearr; /* `File`s */
+
} ResourceOwnerData;
@@ -168,6 +350,16 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
+ ResourceArrayInit(&(owner->catrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->catlistrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->relrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->planrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->tupdescarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->snapshotarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->dsmarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->bufferarr), (Datum) (InvalidBuffer));
+ ResourceArrayInit(&(owner->filearr), (Datum) (-1));
+
return owner;
}
@@ -229,6 +421,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceOwner child;
ResourceOwner save;
ResourceReleaseCallbackItem *item;
+ Datum foundres;
/* Recurse to handle descendants */
for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -252,45 +445,34 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* During a commit, there shouldn't be any remaining pins --- that
* would indicate failure to clean up the executor correctly --- so
* issue warnings. In the abort case, just clean up quietly.
- *
- * We are careful to do the releasing back-to-front, so as to avoid
- * O(N^2) behavior in ResourceOwnerForgetBuffer().
*/
- while (owner->nbuffers > 0)
+ while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
{
+ Buffer res = (Buffer) foundres;
+
if (isCommit)
- PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
- ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
+ PrintBufferLeakWarning(res);
+ ReleaseBuffer(res);
}
- /*
- * Release relcache references. Note that RelationClose will remove
- * the relref entry from my list, so I just have to iterate till there
- * are none.
- *
- * As with buffer pins, warn if any are left at commit time, and
- * release back-to-front for speed.
- */
- while (owner->nrelrefs > 0)
+ /* Ditto for relcache references. */
+ while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
{
+ Relation res = (Relation) foundres;
+
if (isCommit)
- PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]);
- RelationClose(owner->relrefs[owner->nrelrefs - 1]);
+ PrintRelCacheLeakWarning(res);
+ RelationClose(res);
}
- /*
- * Release dynamic shared memory segments. Note that dsm_detach()
- * will remove the segment from my list, so I just have to iterate
- * until there are none.
- *
- * As in the preceding cases, warn if there are leftover at commit
- * time.
- */
- while (owner->ndsms > 0)
+ /* Ditto for dynamic shared memory segments */
+ while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
{
+ dsm_segment *res = (dsm_segment *) foundres;
+
if (isCommit)
- PrintDSMLeakWarning(owner->dsms[owner->ndsms - 1]);
- dsm_detach(owner->dsms[owner->ndsms - 1]);
+ PrintDSMLeakWarning(res);
+ dsm_detach(res);
}
}
else if (phase == RESOURCE_RELEASE_LOCKS)
@@ -351,47 +533,63 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
- while (owner->ncatrefs > 0)
+ while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
{
+ HeapTuple res = (HeapTuple) foundres;
+
if (isCommit)
- PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]);
- ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
+ PrintCatCacheLeakWarning(res);
+ ReleaseCatCache(res);
}
+
/* Ditto for catcache lists */
- while (owner->ncatlistrefs > 0)
+ while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
{
+ CatCList *res = (CatCList *) foundres;
+
if (isCommit)
- PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
- ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
+ PrintCatCacheListLeakWarning(res);
+ ReleaseCatCacheList(res);
}
+
/* Ditto for plancache references */
- while (owner->nplanrefs > 0)
+ while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
{
+ CachedPlan *res = (CachedPlan *) foundres;
+
if (isCommit)
- PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
- ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
+ PrintPlanCacheLeakWarning(res);
+ ReleaseCachedPlan(res, true);
}
+
/* Ditto for tupdesc references */
- while (owner->ntupdescs > 0)
+ while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
{
+ TupleDesc res = (TupleDesc) foundres;
+
if (isCommit)
- PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
- DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
+ PrintTupleDescLeakWarning(res);
+ DecrTupleDescRefCount(res);
}
+
/* Ditto for snapshot references */
- while (owner->nsnapshots > 0)
+ while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
{
+ Snapshot res = (Snapshot) foundres;
+
if (isCommit)
- PrintSnapshotLeakWarning(owner->snapshots[owner->nsnapshots - 1]);
- UnregisterSnapshot(owner->snapshots[owner->nsnapshots - 1]);
+ PrintSnapshotLeakWarning(res);
+ UnregisterSnapshot(res);
}
/* Ditto for temporary files */
- while (owner->nfiles > 0)
+ while (ResourceArrayGetAny(&(owner->filearr), &foundres))
{
+ File res = (File) foundres;
+
if (isCommit)
- PrintFileLeakWarning(owner->files[owner->nfiles - 1]);
- FileClose(owner->files[owner->nfiles - 1]);
+ PrintFileLeakWarning(res);
+ FileClose(res);
}
/* Clean up index scans too */
@@ -418,16 +616,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner != CurrentResourceOwner);
/* And it better not own any resources, either */
- Assert(owner->nbuffers == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
- Assert(owner->ncatrefs == 0);
- Assert(owner->ncatlistrefs == 0);
- Assert(owner->nrelrefs == 0);
- Assert(owner->ndsms == 0);
- Assert(owner->nplanrefs == 0);
- Assert(owner->ntupdescs == 0);
- Assert(owner->nsnapshots == 0);
- Assert(owner->nfiles == 0);
/*
* Delete children. The recursive call will delink the child from me, so
@@ -444,25 +633,15 @@ ResourceOwnerDelete(ResourceOwner owner)
ResourceOwnerNewParent(owner, NULL);
/* And free the object. */
- if (owner->buffers)
- pfree(owner->buffers);
- if (owner->catrefs)
- pfree(owner->catrefs);
- if (owner->catlistrefs)
- pfree(owner->catlistrefs);
- if (owner->relrefs)
- pfree(owner->relrefs);
- if (owner->planrefs)
- pfree(owner->planrefs);
- if (owner->tupdescs)
- pfree(owner->tupdescs);
- if (owner->snapshots)
- pfree(owner->snapshots);
- if (owner->files)
- pfree(owner->files);
- if (owner->dsms)
- pfree(owner->dsms);
-
+ ResourceArrayFree(&(owner->catrefarr));
+ ResourceArrayFree(&(owner->catlistrefarr));
+ ResourceArrayFree(&(owner->relrefarr));
+ ResourceArrayFree(&(owner->planrefarr));
+ ResourceArrayFree(&(owner->tupdescarr));
+ ResourceArrayFree(&(owner->snapshotarr));
+ ResourceArrayFree(&(owner->dsmarr));
+ ResourceArrayFree(&(owner->bufferarr));
+ ResourceArrayFree(&(owner->filearr));
pfree(owner);
}
@@ -575,26 +754,9 @@ UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
void
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
{
- int newmax;
-
- if (owner == NULL ||
- owner->nbuffers < owner->maxbuffers)
- return; /* nothing to do */
-
- if (owner->buffers == NULL)
- {
- newmax = 16;
- owner->buffers = (Buffer *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
- else
- {
- newmax = owner->maxbuffers * 2;
- owner->buffers = (Buffer *)
- repalloc(owner->buffers, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayEnlarge(&(owner->bufferarr));
}
/*
@@ -608,12 +770,9 @@ ResourceOwnerEnlargeBuffers(ResourceOwner owner)
void
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Assert(owner->nbuffers < owner->maxbuffers);
- owner->buffers[owner->nbuffers] = buffer;
- owner->nbuffers++;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayAdd(&(owner->bufferarr), (Datum) buffer);
}
/*
@@ -625,33 +784,15 @@ ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Buffer *buffers = owner->buffers;
- int nb1 = owner->nbuffers - 1;
- int i;
+ bool res;
- /*
- * Scan back-to-front because it's more likely we are releasing a
- * recently pinned buffer. This isn't always the case of course, but
- * it's the way to bet.
- */
- for (i = nb1; i >= 0; i--)
- {
- if (buffers[i] == buffer)
- {
- while (i < nb1)
- {
- buffers[i] = buffers[i + 1];
- i++;
- }
- owner->nbuffers = nb1;
- return;
- }
- }
+ if (owner == NULL)
+ return;
+
+ res = ResourceArrayRemove(&(owner->bufferarr), (Datum) buffer);
+ if (!res)
elog(ERROR, "buffer %d is not owned by resource owner %s",
buffer, owner->name);
- }
}
/*
@@ -667,6 +808,8 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
{
+ Assert(locallock != NULL);
+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have already overflowed */
@@ -714,25 +857,7 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
void
ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatrefs < owner->maxcatrefs)
- return; /* nothing to do */
-
- if (owner->catrefs == NULL)
- {
- newmax = 16;
- owner->catrefs = (HeapTuple *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatrefs * 2;
- owner->catrefs = (HeapTuple *)
- repalloc(owner->catrefs, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catrefarr));
}
/*
@@ -743,9 +868,7 @@ ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- Assert(owner->ncatrefs < owner->maxcatrefs);
- owner->catrefs[owner->ncatrefs] = tuple;
- owner->ncatrefs++;
+ ResourceArrayAdd(&(owner->catrefarr), (Datum) tuple);
}
/*
@@ -754,25 +877,12 @@ ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- HeapTuple *catrefs = owner->catrefs;
- int nc1 = owner->ncatrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catrefarr),
+ (Datum) tuple);
- for (i = nc1; i >= 0; i--)
- {
- if (catrefs[i] == tuple)
- {
- while (i < nc1)
- {
- catrefs[i] = catrefs[i + 1];
- i++;
- }
- owner->ncatrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache reference %p is not owned by resource owner %s",
- tuple, owner->name);
+ if (!res)
+ elog(ERROR, "catcache reference %p is not owned by resource owner %s",
+ tuple, owner->name);
}
/*
@@ -785,25 +895,7 @@ ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatlistrefs < owner->maxcatlistrefs)
- return; /* nothing to do */
-
- if (owner->catlistrefs == NULL)
- {
- newmax = 16;
- owner->catlistrefs = (CatCList **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatlistrefs * 2;
- owner->catlistrefs = (CatCList **)
- repalloc(owner->catlistrefs, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catlistrefarr));
}
/*
@@ -814,9 +906,7 @@ ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- Assert(owner->ncatlistrefs < owner->maxcatlistrefs);
- owner->catlistrefs[owner->ncatlistrefs] = list;
- owner->ncatlistrefs++;
+ ResourceArrayAdd(&(owner->catlistrefarr), (Datum) list);
}
/*
@@ -825,25 +915,12 @@ ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- CatCList **catlistrefs = owner->catlistrefs;
- int nc1 = owner->ncatlistrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catlistrefarr),
+ (Datum) list);
- for (i = nc1; i >= 0; i--)
- {
- if (catlistrefs[i] == list)
- {
- while (i < nc1)
- {
- catlistrefs[i] = catlistrefs[i + 1];
- i++;
- }
- owner->ncatlistrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
- list, owner->name);
+ if (!res)
+ elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
+ list, owner->name);
}
/*
@@ -856,25 +933,7 @@ ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nrelrefs < owner->maxrelrefs)
- return; /* nothing to do */
-
- if (owner->relrefs == NULL)
- {
- newmax = 16;
- owner->relrefs = (Relation *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
- else
- {
- newmax = owner->maxrelrefs * 2;
- owner->relrefs = (Relation *)
- repalloc(owner->relrefs, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->relrefarr));
}
/*
@@ -885,9 +944,7 @@ ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
- Assert(owner->nrelrefs < owner->maxrelrefs);
- owner->relrefs[owner->nrelrefs] = rel;
- owner->nrelrefs++;
+ ResourceArrayAdd(&(owner->relrefarr), (Datum) rel);
}
/*
@@ -896,25 +953,12 @@ ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
- Relation *relrefs = owner->relrefs;
- int nr1 = owner->nrelrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->relrefarr),
+ (Datum) rel);
- for (i = nr1; i >= 0; i--)
- {
- if (relrefs[i] == rel)
- {
- while (i < nr1)
- {
- relrefs[i] = relrefs[i + 1];
- i++;
- }
- owner->nrelrefs = nr1;
- return;
- }
- }
- elog(ERROR, "relcache reference %s is not owned by resource owner %s",
- RelationGetRelationName(rel), owner->name);
+ if (!res)
+ elog(ERROR, "relcache reference %s is not owned by resource owner %s",
+ RelationGetRelationName(rel), owner->name);
}
/*
@@ -937,25 +981,7 @@ PrintRelCacheLeakWarning(Relation rel)
void
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nplanrefs < owner->maxplanrefs)
- return; /* nothing to do */
-
- if (owner->planrefs == NULL)
- {
- newmax = 16;
- owner->planrefs = (CachedPlan **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
- else
- {
- newmax = owner->maxplanrefs * 2;
- owner->planrefs = (CachedPlan **)
- repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->planrefarr));
}
/*
@@ -966,9 +992,7 @@ ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- Assert(owner->nplanrefs < owner->maxplanrefs);
- owner->planrefs[owner->nplanrefs] = plan;
- owner->nplanrefs++;
+ ResourceArrayAdd(&(owner->planrefarr), (Datum) plan);
}
/*
@@ -977,25 +1001,12 @@ ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
void
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- CachedPlan **planrefs = owner->planrefs;
- int np1 = owner->nplanrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->planrefarr),
+ (Datum) plan);
- for (i = np1; i >= 0; i--)
- {
- if (planrefs[i] == plan)
- {
- while (i < np1)
- {
- planrefs[i] = planrefs[i + 1];
- i++;
- }
- owner->nplanrefs = np1;
- return;
- }
- }
- elog(ERROR, "plancache reference %p is not owned by resource owner %s",
- plan, owner->name);
+ if (!res)
+ elog(ERROR, "plancache reference %p is not owned by resource owner %s",
+ plan, owner->name);
}
/*
@@ -1017,25 +1028,7 @@ PrintPlanCacheLeakWarning(CachedPlan *plan)
void
ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ntupdescs < owner->maxtupdescs)
- return; /* nothing to do */
-
- if (owner->tupdescs == NULL)
- {
- newmax = 16;
- owner->tupdescs = (TupleDesc *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
- else
- {
- newmax = owner->maxtupdescs * 2;
- owner->tupdescs = (TupleDesc *)
- repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->tupdescarr));
}
/*
@@ -1046,9 +1039,7 @@ ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
void
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- Assert(owner->ntupdescs < owner->maxtupdescs);
- owner->tupdescs[owner->ntupdescs] = tupdesc;
- owner->ntupdescs++;
+ ResourceArrayAdd(&(owner->tupdescarr), (Datum) tupdesc);
}
/*
@@ -1057,25 +1048,12 @@ ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
void
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- TupleDesc *tupdescs = owner->tupdescs;
- int nt1 = owner->ntupdescs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->tupdescarr),
+ (Datum) tupdesc);
- for (i = nt1; i >= 0; i--)
- {
- if (tupdescs[i] == tupdesc)
- {
- while (i < nt1)
- {
- tupdescs[i] = tupdescs[i + 1];
- i++;
- }
- owner->ntupdescs = nt1;
- return;
- }
- }
- elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
- tupdesc, owner->name);
+ if (!res)
+ elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
+ tupdesc, owner->name);
}
/*
@@ -1099,25 +1077,7 @@ PrintTupleDescLeakWarning(TupleDesc tupdesc)
void
ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nsnapshots < owner->maxsnapshots)
- return; /* nothing to do */
-
- if (owner->snapshots == NULL)
- {
- newmax = 16;
- owner->snapshots = (Snapshot *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
- else
- {
- newmax = owner->maxsnapshots * 2;
- owner->snapshots = (Snapshot *)
- repalloc(owner->snapshots, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
+ ResourceArrayEnlarge(&(owner->snapshotarr));
}
/*
@@ -1128,9 +1088,7 @@ ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
void
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Assert(owner->nsnapshots < owner->maxsnapshots);
- owner->snapshots[owner->nsnapshots] = snapshot;
- owner->nsnapshots++;
+ ResourceArrayAdd(&(owner->snapshotarr), (Datum) snapshot);
}
/*
@@ -1139,25 +1097,12 @@ ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
void
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Snapshot *snapshots = owner->snapshots;
- int ns1 = owner->nsnapshots - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->snapshotarr),
+ (Datum) snapshot);
- for (i = ns1; i >= 0; i--)
- {
- if (snapshots[i] == snapshot)
- {
- while (i < ns1)
- {
- snapshots[i] = snapshots[i + 1];
- i++;
- }
- owner->nsnapshots = ns1;
- return;
- }
- }
- elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
- snapshot, owner->name);
+ if (!res)
+ elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
+ snapshot, owner->name);
}
/*
@@ -1182,25 +1127,7 @@ PrintSnapshotLeakWarning(Snapshot snapshot)
void
ResourceOwnerEnlargeFiles(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nfiles < owner->maxfiles)
- return; /* nothing to do */
-
- if (owner->files == NULL)
- {
- newmax = 16;
- owner->files = (File *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
- else
- {
- newmax = owner->maxfiles * 2;
- owner->files = (File *)
- repalloc(owner->files, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
+ ResourceArrayEnlarge(&(owner->filearr));
}
/*
@@ -1211,9 +1138,7 @@ ResourceOwnerEnlargeFiles(ResourceOwner owner)
void
ResourceOwnerRememberFile(ResourceOwner owner, File file)
{
- Assert(owner->nfiles < owner->maxfiles);
- owner->files[owner->nfiles] = file;
- owner->nfiles++;
+ ResourceArrayAdd(&(owner->filearr), (Datum) file);
}
/*
@@ -1222,25 +1147,12 @@ ResourceOwnerRememberFile(ResourceOwner owner, File file)
void
ResourceOwnerForgetFile(ResourceOwner owner, File file)
{
- File *files = owner->files;
- int ns1 = owner->nfiles - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->filearr),
+ (Datum) file);
- for (i = ns1; i >= 0; i--)
- {
- if (files[i] == file)
- {
- while (i < ns1)
- {
- files[i] = files[i + 1];
- i++;
- }
- owner->nfiles = ns1;
- return;
- }
- }
- elog(ERROR, "temporery file %d is not owned by resource owner %s",
- file, owner->name);
+ if (!res)
+ elog(ERROR, "temporary file %d is not owned by resource owner %s",
+ file, owner->name);
}
@@ -1265,26 +1177,7 @@ PrintFileLeakWarning(File file)
void
ResourceOwnerEnlargeDSMs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ndsms < owner->maxdsms)
- return; /* nothing to do */
-
- if (owner->dsms == NULL)
- {
- newmax = 16;
- owner->dsms = (dsm_segment **)
- MemoryContextAlloc(TopMemoryContext,
- newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
- else
- {
- newmax = owner->maxdsms * 2;
- owner->dsms = (dsm_segment **)
- repalloc(owner->dsms, newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
+ ResourceArrayEnlarge(&(owner->dsmarr));
}
/*
@@ -1295,9 +1188,7 @@ ResourceOwnerEnlargeDSMs(ResourceOwner owner)
void
ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
{
- Assert(owner->ndsms < owner->maxdsms);
- owner->dsms[owner->ndsms] = seg;
- owner->ndsms++;
+ ResourceArrayAdd(&(owner->dsmarr), (Datum) seg);
}
/*
@@ -1306,26 +1197,12 @@ ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
void
ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
{
- dsm_segment **dsms = owner->dsms;
- int ns1 = owner->ndsms - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->dsmarr),
+ (Datum) seg);
- for (i = ns1; i >= 0; i--)
- {
- if (dsms[i] == seg)
- {
- while (i < ns1)
- {
- dsms[i] = dsms[i + 1];
- i++;
- }
- owner->ndsms = ns1;
- return;
- }
- }
- elog(ERROR,
- "dynamic shared memory segment %u is not owned by resource owner %s",
- dsm_segment_handle(seg), owner->name);
+ if (!res)
+ elog(ERROR, "dynamic shared memory segment %u is not owned by resource"
+ " owner %s", dsm_segment_handle(seg), owner->name);
}
resource-owner-optimization-v4-step2.patchtext/x-patchDownload
diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c
index 9ee654e..5a00a03 100644
--- a/src/backend/access/hash/hashfunc.c
+++ b/src/backend/access/hash/hashfunc.c
@@ -297,6 +297,9 @@ hashvarlena(PG_FUNCTION_ARGS)
* of 2. There is no need to do mod a prime (mod is sooo slow!).
* If you need less than 32 bits, use a bitmask.
*
+ * This procedure never fails. Its important. Some code notably ResourceOwner
+ * relies on this.
+ *
* Note: we could easily change this function to return a 64-bit hash value
* by using the final values of both b and c. b is perhaps a little less
* well mixed than c, however.
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 3330c8d..abed36c 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -37,29 +37,60 @@
* which should be called before corresponding ResourceOwnerRemember* calls
* (see below). Internally each type of resource is stored in separate
* ResourceArray.
+ *
+ * There are two major reasons for using ResourceArray instead of, say,
+ * regular C arrays.
+ *
+ * Firstly we would like to prevent code duplication. For instance
+ * ResourceArray provides generic Remember/Forget/Enlarge procedures, so
+ * corresponding ResourceOwner* procedures are just a typesafe wrappers for
+ * these procedures.
+ *
+ * Secondly ResourceArray must be more efficient than regular C array.
+ * Current implementation in general could be considered a hash table. It has
+ * O(1) complexity of both Remember and Forget procedures.
*/
typedef struct ResourceArray
{
Datum *itemsarr; /* buffer for storing values */
+ Datum invalidval; /* value that is considered invalid */
uint32 capacity; /* capacity of array */
uint32 nitems; /* how many items is stored in items array */
+ uint32 maxitems; /* precalculated RESARRAY_MAX_ITEMS(capacity) */
+ uint32 lastidx; /* index of last item returned by GetAny */
} ResourceArray;
/*
* This number is used as initial size of resource array. If given number of
* items is not enough, we double array size and reallocate memory.
+ *
+ * Should be power of two since we use (arrsize - 1) as mask for hash value.
+ *
*/
#define RESARRAY_INIT_SIZE 16
/*
+ * How many items could be stored in a resource array of given capacity. If
+ * this number is reached we need to resize an array to prevent hash collisions.
+ *
+ * This computation actually costs only two additions and one binary shift.
+ */
+#define RESARRAY_MAX_ITEMS(capacity) ((capacity)*3/4)
+
+/*
* Initialize ResourceArray
*/
static void
-ResourceArrayInit(ResourceArray * resarr)
+ResourceArrayInit(ResourceArray * resarr, Datum invalidval)
{
Assert(resarr->itemsarr == NULL);
Assert(resarr->capacity == 0);
Assert(resarr->nitems == 0);
+ Assert(resarr->maxitems == 0);
+ Assert(resarr->invalidval == 0);
+ Assert(resarr->lastidx == 0);
+
+ resarr->invalidval = invalidval;
}
/*
@@ -70,11 +101,24 @@ ResourceArrayInit(ResourceArray * resarr)
static void
ResourceArrayAdd(ResourceArray * resarr, Datum data)
{
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
+
+ Assert(resarr->maxitems > resarr->nitems);
Assert(resarr->capacity > 0);
Assert(resarr->itemsarr != NULL);
- Assert(resarr->nitems < resarr->capacity);
+ Assert(data != resarr->invalidval);
+
+ idx = hash_any((void *) &data, sizeof(data)) & mask;
+
+ while (true)
+ {
+ if (resarr->itemsarr[idx] == resarr->invalidval)
+ break;
+ idx = (idx + 1) & mask;
+ }
- resarr->itemsarr[resarr->nitems] = data;
+ resarr->itemsarr[idx] = data;
resarr->nitems++;
}
@@ -86,24 +130,24 @@ ResourceArrayAdd(ResourceArray * resarr, Datum data)
static bool
ResourceArrayRemove(ResourceArray * resarr, Datum data)
{
- int i,
- j,
- lastidx;
+ uint32 i;
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
Assert(resarr->capacity > 0);
Assert(resarr->itemsarr != NULL);
+ Assert(data != resarr->invalidval);
- lastidx = ((int) resarr->nitems) - 1;
-
- for (i = lastidx; i >= 0; i--)
+ idx = hash_any((void *) &data, sizeof(data)) & mask;
+ for (i = 0; i < resarr->capacity; i++)
{
- if (resarr->itemsarr[i] == data)
+ if (resarr->itemsarr[idx] == data)
{
- for (j = i; j < lastidx; j++)
- resarr->itemsarr[j] = resarr->itemsarr[j + 1];
+ resarr->itemsarr[idx] = resarr->invalidval;
resarr->nitems--;
return true;
}
+ idx = (idx + 1) & mask;
}
return false;
@@ -119,27 +163,33 @@ static void
ResourceArrayEnlarge(ResourceArray * resarr)
{
uint32 i,
- oldcap,
- oldnitems;
+ oldcap;
Datum *olditemsarr;
- if (resarr->nitems < resarr->capacity)
+ if (resarr->nitems < resarr->maxitems)
return; /* nothing to do */
olditemsarr = resarr->itemsarr;
oldcap = resarr->capacity;
- oldnitems = resarr->nitems;
resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
resarr->itemsarr = (Datum *)
MemoryContextAlloc(TopMemoryContext,
resarr->capacity * sizeof(Datum));
+ resarr->maxitems = RESARRAY_MAX_ITEMS(resarr->capacity);
resarr->nitems = 0;
+ for (i = 0; i < resarr->capacity; i++)
+ resarr->itemsarr[i] = resarr->invalidval;
+
if (olditemsarr != NULL)
{
- for (i = 0; i < oldnitems; i++)
- ResourceArrayAdd(resarr, olditemsarr[i]);
+ while (oldcap > 0)
+ {
+ oldcap--;
+ if (olditemsarr[oldcap] != resarr->invalidval)
+ ResourceArrayAdd(resarr, olditemsarr[oldcap]);
+ }
pfree(olditemsarr);
}
}
@@ -152,12 +202,24 @@ ResourceArrayEnlarge(ResourceArray * resarr)
static bool
ResourceArrayGetAny(ResourceArray * resarr, Datum *out)
{
+ uint32 mask;
+
if (resarr->nitems == 0)
return false;
Assert(resarr->capacity > 0);
+ mask = resarr->capacity - 1;
+
+ for (;;)
+ {
+ resarr->lastidx = resarr->lastidx & mask;
+ if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
+ break;
+
+ resarr->lastidx++;
+ }
- *out = resarr->itemsarr[resarr->nitems - 1];
+ *out = resarr->itemsarr[resarr->lastidx];
return true;
}
@@ -170,6 +232,7 @@ ResourceArrayFree(ResourceArray * resarr)
Assert(resarr->nitems == 0);
resarr->capacity = 0;
+ resarr->maxitems = 0;
if (!resarr->itemsarr)
return;
@@ -287,15 +350,15 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
- ResourceArrayInit(&(owner->catrefarr));
- ResourceArrayInit(&(owner->catlistrefarr));
- ResourceArrayInit(&(owner->relrefarr));
- ResourceArrayInit(&(owner->planrefarr));
- ResourceArrayInit(&(owner->tupdescarr));
- ResourceArrayInit(&(owner->snapshotarr));
- ResourceArrayInit(&(owner->dsmarr));
- ResourceArrayInit(&(owner->bufferarr));
- ResourceArrayInit(&(owner->filearr));
+ ResourceArrayInit(&(owner->catrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->catlistrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->relrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->planrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->tupdescarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->snapshotarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->dsmarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->bufferarr), (Datum) (InvalidBuffer));
+ ResourceArrayInit(&(owner->filearr), (Datum) (-1));
return owner;
}
Oops, wrong patches - here are correct ones.
Attachments:
resource-owner-optimization-v4-step1.patchtext/x-patchDownload
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 0e7acbf..3330c8d 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -29,6 +29,156 @@
#include "utils/snapmgr.h"
/*
+ * ResourceArray is a common structure for storing different types of resources.
+ *
+ * ResourceOwner can own `HeapTuple`s, `Relation`s, `Snapshot`s, etc. For
+ * each resource type there are procedures ResourceOwnerRemember* and
+ * ResourceOwnerForget*. There are also ResourceOwnerEnlarge* procedures
+ * which should be called before corresponding ResourceOwnerRemember* calls
+ * (see below). Internally each type of resource is stored in separate
+ * ResourceArray.
+ */
+typedef struct ResourceArray
+{
+ Datum *itemsarr; /* buffer for storing values */
+ uint32 capacity; /* capacity of array */
+ uint32 nitems; /* how many items is stored in items array */
+} ResourceArray;
+
+/*
+ * This number is used as initial size of resource array. If given number of
+ * items is not enough, we double array size and reallocate memory.
+ */
+#define RESARRAY_INIT_SIZE 16
+
+/*
+ * Initialize ResourceArray
+ */
+static void
+ResourceArrayInit(ResourceArray * resarr)
+{
+ Assert(resarr->itemsarr == NULL);
+ Assert(resarr->capacity == 0);
+ Assert(resarr->nitems == 0);
+}
+
+/*
+ * Add a resource to ResourceArray
+ *
+ * Caller must have previously done ResourceArrayEnlarge()
+ */
+static void
+ResourceArrayAdd(ResourceArray * resarr, Datum data)
+{
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+ Assert(resarr->nitems < resarr->capacity);
+
+ resarr->itemsarr[resarr->nitems] = data;
+ resarr->nitems++;
+}
+
+/*
+ * Remove a resource from ResourceArray
+ *
+ * Returns true on success, false if resource was not found
+ */
+static bool
+ResourceArrayRemove(ResourceArray * resarr, Datum data)
+{
+ int i,
+ j,
+ lastidx;
+
+ Assert(resarr->capacity > 0);
+ Assert(resarr->itemsarr != NULL);
+
+ lastidx = ((int) resarr->nitems) - 1;
+
+ for (i = lastidx; i >= 0; i--)
+ {
+ if (resarr->itemsarr[i] == data)
+ {
+ for (j = i; j < lastidx; j++)
+ resarr->itemsarr[j] = resarr->itemsarr[j + 1];
+ resarr->nitems--;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * Make sure there is a room for at least one more resource in an array.
+ *
+ * This is separate from actually inserting a resource because if we run out
+ * of memory, it's critical to do so *before* acquiring the resource.
+ */
+static void
+ResourceArrayEnlarge(ResourceArray * resarr)
+{
+ uint32 i,
+ oldcap,
+ oldnitems;
+ Datum *olditemsarr;
+
+ if (resarr->nitems < resarr->capacity)
+ return; /* nothing to do */
+
+ olditemsarr = resarr->itemsarr;
+ oldcap = resarr->capacity;
+ oldnitems = resarr->nitems;
+
+ resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
+ resarr->itemsarr = (Datum *)
+ MemoryContextAlloc(TopMemoryContext,
+ resarr->capacity * sizeof(Datum));
+ resarr->nitems = 0;
+
+ if (olditemsarr != NULL)
+ {
+ for (i = 0; i < oldnitems; i++)
+ ResourceArrayAdd(resarr, olditemsarr[i]);
+ pfree(olditemsarr);
+ }
+}
+
+/*
+ * Get any convenient element.
+ *
+ * Returns true on success, false on failure.
+ */
+static bool
+ResourceArrayGetAny(ResourceArray * resarr, Datum *out)
+{
+ if (resarr->nitems == 0)
+ return false;
+
+ Assert(resarr->capacity > 0);
+
+ *out = resarr->itemsarr[resarr->nitems - 1];
+ return true;
+}
+
+/*
+ * Return ResourceArray to initial state
+ */
+static void
+ResourceArrayFree(ResourceArray * resarr)
+{
+ Assert(resarr->nitems == 0);
+
+ resarr->capacity = 0;
+
+ if (!resarr->itemsarr)
+ return;
+
+ pfree(resarr->itemsarr);
+ resarr->itemsarr = NULL;
+}
+
+/*
* To speed up bulk releasing or reassigning locks from a resource owner to
* its parent, each resource owner has a small cache of locks it owns. The
* lock manager has the same information in its local lock hash table, and
@@ -56,53 +206,22 @@ typedef struct ResourceOwnerData
ResourceOwner nextchild; /* next child of same parent */
const char *name; /* name (just for debugging) */
- /* We have built-in support for remembering owned buffers */
- int nbuffers; /* number of owned buffer pins */
- Buffer *buffers; /* dynamically allocated array */
- int maxbuffers; /* currently allocated array size */
-
/* We can remember up to MAX_RESOWNER_LOCKS references to local locks. */
int nlocks; /* number of owned locks */
LOCALLOCK *locks[MAX_RESOWNER_LOCKS]; /* list of owned locks */
- /* We have built-in support for remembering catcache references */
- int ncatrefs; /* number of owned catcache pins */
- HeapTuple *catrefs; /* dynamically allocated array */
- int maxcatrefs; /* currently allocated array size */
-
- int ncatlistrefs; /* number of owned catcache-list pins */
- CatCList **catlistrefs; /* dynamically allocated array */
- int maxcatlistrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering relcache references */
- int nrelrefs; /* number of owned relcache pins */
- Relation *relrefs; /* dynamically allocated array */
- int maxrelrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering plancache references */
- int nplanrefs; /* number of owned plancache pins */
- CachedPlan **planrefs; /* dynamically allocated array */
- int maxplanrefs; /* currently allocated array size */
-
- /* We have built-in support for remembering tupdesc references */
- int ntupdescs; /* number of owned tupdesc references */
- TupleDesc *tupdescs; /* dynamically allocated array */
- int maxtupdescs; /* currently allocated array size */
-
- /* We have built-in support for remembering snapshot references */
- int nsnapshots; /* number of owned snapshot references */
- Snapshot *snapshots; /* dynamically allocated array */
- int maxsnapshots; /* currently allocated array size */
-
- /* We have built-in support for remembering open temporary files */
- int nfiles; /* number of owned temporary files */
- File *files; /* dynamically allocated array */
- int maxfiles; /* currently allocated array size */
-
- /* We have built-in support for remembering dynamic shmem segments */
- int ndsms; /* number of owned shmem segments */
- dsm_segment **dsms; /* dynamically allocated array */
- int maxdsms; /* currently allocated array size */
+ /* We have built-in support for remembering: */
+
+ ResourceArray catrefarr; /* `HeapTuple`s */
+ ResourceArray catlistrefarr; /* `ResourceOwner`s */
+ ResourceArray relrefarr; /* `Relation`s */
+ ResourceArray planrefarr; /* `CachedPlan*`s */
+ ResourceArray tupdescarr; /* `TupleDesc`s */
+ ResourceArray snapshotarr; /* `Snapshot`s */
+ ResourceArray dsmarr; /* `dsm_segment*`s */
+ ResourceArray bufferarr; /* `Buffer`s */
+ ResourceArray filearr; /* `File`s */
+
} ResourceOwnerData;
@@ -168,6 +287,16 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
+ ResourceArrayInit(&(owner->catrefarr));
+ ResourceArrayInit(&(owner->catlistrefarr));
+ ResourceArrayInit(&(owner->relrefarr));
+ ResourceArrayInit(&(owner->planrefarr));
+ ResourceArrayInit(&(owner->tupdescarr));
+ ResourceArrayInit(&(owner->snapshotarr));
+ ResourceArrayInit(&(owner->dsmarr));
+ ResourceArrayInit(&(owner->bufferarr));
+ ResourceArrayInit(&(owner->filearr));
+
return owner;
}
@@ -229,6 +358,7 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
ResourceOwner child;
ResourceOwner save;
ResourceReleaseCallbackItem *item;
+ Datum foundres;
/* Recurse to handle descendants */
for (child = owner->firstchild; child != NULL; child = child->nextchild)
@@ -252,45 +382,34 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* During a commit, there shouldn't be any remaining pins --- that
* would indicate failure to clean up the executor correctly --- so
* issue warnings. In the abort case, just clean up quietly.
- *
- * We are careful to do the releasing back-to-front, so as to avoid
- * O(N^2) behavior in ResourceOwnerForgetBuffer().
*/
- while (owner->nbuffers > 0)
+ while (ResourceArrayGetAny(&(owner->bufferarr), &foundres))
{
+ Buffer res = (Buffer) foundres;
+
if (isCommit)
- PrintBufferLeakWarning(owner->buffers[owner->nbuffers - 1]);
- ReleaseBuffer(owner->buffers[owner->nbuffers - 1]);
+ PrintBufferLeakWarning(res);
+ ReleaseBuffer(res);
}
- /*
- * Release relcache references. Note that RelationClose will remove
- * the relref entry from my list, so I just have to iterate till there
- * are none.
- *
- * As with buffer pins, warn if any are left at commit time, and
- * release back-to-front for speed.
- */
- while (owner->nrelrefs > 0)
+ /* Ditto for relcache references. */
+ while (ResourceArrayGetAny(&(owner->relrefarr), &foundres))
{
+ Relation res = (Relation) foundres;
+
if (isCommit)
- PrintRelCacheLeakWarning(owner->relrefs[owner->nrelrefs - 1]);
- RelationClose(owner->relrefs[owner->nrelrefs - 1]);
+ PrintRelCacheLeakWarning(res);
+ RelationClose(res);
}
- /*
- * Release dynamic shared memory segments. Note that dsm_detach()
- * will remove the segment from my list, so I just have to iterate
- * until there are none.
- *
- * As in the preceding cases, warn if there are leftover at commit
- * time.
- */
- while (owner->ndsms > 0)
+ /* Ditto for dynamic shared memory segments */
+ while (ResourceArrayGetAny(&(owner->dsmarr), &foundres))
{
+ dsm_segment *res = (dsm_segment *) foundres;
+
if (isCommit)
- PrintDSMLeakWarning(owner->dsms[owner->ndsms - 1]);
- dsm_detach(owner->dsms[owner->ndsms - 1]);
+ PrintDSMLeakWarning(res);
+ dsm_detach(res);
}
}
else if (phase == RESOURCE_RELEASE_LOCKS)
@@ -351,47 +470,63 @@ ResourceOwnerReleaseInternal(ResourceOwner owner,
* As with buffer pins, warn if any are left at commit time, and
* release back-to-front for speed.
*/
- while (owner->ncatrefs > 0)
+ while (ResourceArrayGetAny(&(owner->catrefarr), &foundres))
{
+ HeapTuple res = (HeapTuple) foundres;
+
if (isCommit)
- PrintCatCacheLeakWarning(owner->catrefs[owner->ncatrefs - 1]);
- ReleaseCatCache(owner->catrefs[owner->ncatrefs - 1]);
+ PrintCatCacheLeakWarning(res);
+ ReleaseCatCache(res);
}
+
/* Ditto for catcache lists */
- while (owner->ncatlistrefs > 0)
+ while (ResourceArrayGetAny(&(owner->catlistrefarr), &foundres))
{
+ CatCList *res = (CatCList *) foundres;
+
if (isCommit)
- PrintCatCacheListLeakWarning(owner->catlistrefs[owner->ncatlistrefs - 1]);
- ReleaseCatCacheList(owner->catlistrefs[owner->ncatlistrefs - 1]);
+ PrintCatCacheListLeakWarning(res);
+ ReleaseCatCacheList(res);
}
+
/* Ditto for plancache references */
- while (owner->nplanrefs > 0)
+ while (ResourceArrayGetAny(&(owner->planrefarr), &foundres))
{
+ CachedPlan *res = (CachedPlan *) foundres;
+
if (isCommit)
- PrintPlanCacheLeakWarning(owner->planrefs[owner->nplanrefs - 1]);
- ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], true);
+ PrintPlanCacheLeakWarning(res);
+ ReleaseCachedPlan(res, true);
}
+
/* Ditto for tupdesc references */
- while (owner->ntupdescs > 0)
+ while (ResourceArrayGetAny(&(owner->tupdescarr), &foundres))
{
+ TupleDesc res = (TupleDesc) foundres;
+
if (isCommit)
- PrintTupleDescLeakWarning(owner->tupdescs[owner->ntupdescs - 1]);
- DecrTupleDescRefCount(owner->tupdescs[owner->ntupdescs - 1]);
+ PrintTupleDescLeakWarning(res);
+ DecrTupleDescRefCount(res);
}
+
/* Ditto for snapshot references */
- while (owner->nsnapshots > 0)
+ while (ResourceArrayGetAny(&(owner->snapshotarr), &foundres))
{
+ Snapshot res = (Snapshot) foundres;
+
if (isCommit)
- PrintSnapshotLeakWarning(owner->snapshots[owner->nsnapshots - 1]);
- UnregisterSnapshot(owner->snapshots[owner->nsnapshots - 1]);
+ PrintSnapshotLeakWarning(res);
+ UnregisterSnapshot(res);
}
/* Ditto for temporary files */
- while (owner->nfiles > 0)
+ while (ResourceArrayGetAny(&(owner->filearr), &foundres))
{
+ File res = (File) foundres;
+
if (isCommit)
- PrintFileLeakWarning(owner->files[owner->nfiles - 1]);
- FileClose(owner->files[owner->nfiles - 1]);
+ PrintFileLeakWarning(res);
+ FileClose(res);
}
/* Clean up index scans too */
@@ -418,16 +553,7 @@ ResourceOwnerDelete(ResourceOwner owner)
Assert(owner != CurrentResourceOwner);
/* And it better not own any resources, either */
- Assert(owner->nbuffers == 0);
Assert(owner->nlocks == 0 || owner->nlocks == MAX_RESOWNER_LOCKS + 1);
- Assert(owner->ncatrefs == 0);
- Assert(owner->ncatlistrefs == 0);
- Assert(owner->nrelrefs == 0);
- Assert(owner->ndsms == 0);
- Assert(owner->nplanrefs == 0);
- Assert(owner->ntupdescs == 0);
- Assert(owner->nsnapshots == 0);
- Assert(owner->nfiles == 0);
/*
* Delete children. The recursive call will delink the child from me, so
@@ -444,25 +570,15 @@ ResourceOwnerDelete(ResourceOwner owner)
ResourceOwnerNewParent(owner, NULL);
/* And free the object. */
- if (owner->buffers)
- pfree(owner->buffers);
- if (owner->catrefs)
- pfree(owner->catrefs);
- if (owner->catlistrefs)
- pfree(owner->catlistrefs);
- if (owner->relrefs)
- pfree(owner->relrefs);
- if (owner->planrefs)
- pfree(owner->planrefs);
- if (owner->tupdescs)
- pfree(owner->tupdescs);
- if (owner->snapshots)
- pfree(owner->snapshots);
- if (owner->files)
- pfree(owner->files);
- if (owner->dsms)
- pfree(owner->dsms);
-
+ ResourceArrayFree(&(owner->catrefarr));
+ ResourceArrayFree(&(owner->catlistrefarr));
+ ResourceArrayFree(&(owner->relrefarr));
+ ResourceArrayFree(&(owner->planrefarr));
+ ResourceArrayFree(&(owner->tupdescarr));
+ ResourceArrayFree(&(owner->snapshotarr));
+ ResourceArrayFree(&(owner->dsmarr));
+ ResourceArrayFree(&(owner->bufferarr));
+ ResourceArrayFree(&(owner->filearr));
pfree(owner);
}
@@ -575,26 +691,9 @@ UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void *arg)
void
ResourceOwnerEnlargeBuffers(ResourceOwner owner)
{
- int newmax;
-
- if (owner == NULL ||
- owner->nbuffers < owner->maxbuffers)
- return; /* nothing to do */
-
- if (owner->buffers == NULL)
- {
- newmax = 16;
- owner->buffers = (Buffer *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
- else
- {
- newmax = owner->maxbuffers * 2;
- owner->buffers = (Buffer *)
- repalloc(owner->buffers, newmax * sizeof(Buffer));
- owner->maxbuffers = newmax;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayEnlarge(&(owner->bufferarr));
}
/*
@@ -608,12 +707,9 @@ ResourceOwnerEnlargeBuffers(ResourceOwner owner)
void
ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Assert(owner->nbuffers < owner->maxbuffers);
- owner->buffers[owner->nbuffers] = buffer;
- owner->nbuffers++;
- }
+ if (owner == NULL)
+ return;
+ ResourceArrayAdd(&(owner->bufferarr), (Datum) buffer);
}
/*
@@ -625,33 +721,15 @@ ResourceOwnerRememberBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
{
- if (owner != NULL)
- {
- Buffer *buffers = owner->buffers;
- int nb1 = owner->nbuffers - 1;
- int i;
+ bool res;
- /*
- * Scan back-to-front because it's more likely we are releasing a
- * recently pinned buffer. This isn't always the case of course, but
- * it's the way to bet.
- */
- for (i = nb1; i >= 0; i--)
- {
- if (buffers[i] == buffer)
- {
- while (i < nb1)
- {
- buffers[i] = buffers[i + 1];
- i++;
- }
- owner->nbuffers = nb1;
- return;
- }
- }
+ if (owner == NULL)
+ return;
+
+ res = ResourceArrayRemove(&(owner->bufferarr), (Datum) buffer);
+ if (!res)
elog(ERROR, "buffer %d is not owned by resource owner %s",
buffer, owner->name);
- }
}
/*
@@ -667,6 +745,8 @@ ResourceOwnerForgetBuffer(ResourceOwner owner, Buffer buffer)
void
ResourceOwnerRememberLock(ResourceOwner owner, LOCALLOCK *locallock)
{
+ Assert(locallock != NULL);
+
if (owner->nlocks > MAX_RESOWNER_LOCKS)
return; /* we have already overflowed */
@@ -714,25 +794,7 @@ ResourceOwnerForgetLock(ResourceOwner owner, LOCALLOCK *locallock)
void
ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatrefs < owner->maxcatrefs)
- return; /* nothing to do */
-
- if (owner->catrefs == NULL)
- {
- newmax = 16;
- owner->catrefs = (HeapTuple *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatrefs * 2;
- owner->catrefs = (HeapTuple *)
- repalloc(owner->catrefs, newmax * sizeof(HeapTuple));
- owner->maxcatrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catrefarr));
}
/*
@@ -743,9 +805,7 @@ ResourceOwnerEnlargeCatCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- Assert(owner->ncatrefs < owner->maxcatrefs);
- owner->catrefs[owner->ncatrefs] = tuple;
- owner->ncatrefs++;
+ ResourceArrayAdd(&(owner->catrefarr), (Datum) tuple);
}
/*
@@ -754,25 +814,12 @@ ResourceOwnerRememberCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
{
- HeapTuple *catrefs = owner->catrefs;
- int nc1 = owner->ncatrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catrefarr),
+ (Datum) tuple);
- for (i = nc1; i >= 0; i--)
- {
- if (catrefs[i] == tuple)
- {
- while (i < nc1)
- {
- catrefs[i] = catrefs[i + 1];
- i++;
- }
- owner->ncatrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache reference %p is not owned by resource owner %s",
- tuple, owner->name);
+ if (!res)
+ elog(ERROR, "catcache reference %p is not owned by resource owner %s",
+ tuple, owner->name);
}
/*
@@ -785,25 +832,7 @@ ResourceOwnerForgetCatCacheRef(ResourceOwner owner, HeapTuple tuple)
void
ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ncatlistrefs < owner->maxcatlistrefs)
- return; /* nothing to do */
-
- if (owner->catlistrefs == NULL)
- {
- newmax = 16;
- owner->catlistrefs = (CatCList **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
- else
- {
- newmax = owner->maxcatlistrefs * 2;
- owner->catlistrefs = (CatCList **)
- repalloc(owner->catlistrefs, newmax * sizeof(CatCList *));
- owner->maxcatlistrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->catlistrefarr));
}
/*
@@ -814,9 +843,7 @@ ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner owner)
void
ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- Assert(owner->ncatlistrefs < owner->maxcatlistrefs);
- owner->catlistrefs[owner->ncatlistrefs] = list;
- owner->ncatlistrefs++;
+ ResourceArrayAdd(&(owner->catlistrefarr), (Datum) list);
}
/*
@@ -825,25 +852,12 @@ ResourceOwnerRememberCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
{
- CatCList **catlistrefs = owner->catlistrefs;
- int nc1 = owner->ncatlistrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->catlistrefarr),
+ (Datum) list);
- for (i = nc1; i >= 0; i--)
- {
- if (catlistrefs[i] == list)
- {
- while (i < nc1)
- {
- catlistrefs[i] = catlistrefs[i + 1];
- i++;
- }
- owner->ncatlistrefs = nc1;
- return;
- }
- }
- elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
- list, owner->name);
+ if (!res)
+ elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
+ list, owner->name);
}
/*
@@ -856,25 +870,7 @@ ResourceOwnerForgetCatCacheListRef(ResourceOwner owner, CatCList *list)
void
ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nrelrefs < owner->maxrelrefs)
- return; /* nothing to do */
-
- if (owner->relrefs == NULL)
- {
- newmax = 16;
- owner->relrefs = (Relation *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
- else
- {
- newmax = owner->maxrelrefs * 2;
- owner->relrefs = (Relation *)
- repalloc(owner->relrefs, newmax * sizeof(Relation));
- owner->maxrelrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->relrefarr));
}
/*
@@ -885,9 +881,7 @@ ResourceOwnerEnlargeRelationRefs(ResourceOwner owner)
void
ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
{
- Assert(owner->nrelrefs < owner->maxrelrefs);
- owner->relrefs[owner->nrelrefs] = rel;
- owner->nrelrefs++;
+ ResourceArrayAdd(&(owner->relrefarr), (Datum) rel);
}
/*
@@ -896,25 +890,12 @@ ResourceOwnerRememberRelationRef(ResourceOwner owner, Relation rel)
void
ResourceOwnerForgetRelationRef(ResourceOwner owner, Relation rel)
{
- Relation *relrefs = owner->relrefs;
- int nr1 = owner->nrelrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->relrefarr),
+ (Datum) rel);
- for (i = nr1; i >= 0; i--)
- {
- if (relrefs[i] == rel)
- {
- while (i < nr1)
- {
- relrefs[i] = relrefs[i + 1];
- i++;
- }
- owner->nrelrefs = nr1;
- return;
- }
- }
- elog(ERROR, "relcache reference %s is not owned by resource owner %s",
- RelationGetRelationName(rel), owner->name);
+ if (!res)
+ elog(ERROR, "relcache reference %s is not owned by resource owner %s",
+ RelationGetRelationName(rel), owner->name);
}
/*
@@ -937,25 +918,7 @@ PrintRelCacheLeakWarning(Relation rel)
void
ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nplanrefs < owner->maxplanrefs)
- return; /* nothing to do */
-
- if (owner->planrefs == NULL)
- {
- newmax = 16;
- owner->planrefs = (CachedPlan **)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
- else
- {
- newmax = owner->maxplanrefs * 2;
- owner->planrefs = (CachedPlan **)
- repalloc(owner->planrefs, newmax * sizeof(CachedPlan *));
- owner->maxplanrefs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->planrefarr));
}
/*
@@ -966,9 +929,7 @@ ResourceOwnerEnlargePlanCacheRefs(ResourceOwner owner)
void
ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- Assert(owner->nplanrefs < owner->maxplanrefs);
- owner->planrefs[owner->nplanrefs] = plan;
- owner->nplanrefs++;
+ ResourceArrayAdd(&(owner->planrefarr), (Datum) plan);
}
/*
@@ -977,25 +938,12 @@ ResourceOwnerRememberPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
void
ResourceOwnerForgetPlanCacheRef(ResourceOwner owner, CachedPlan *plan)
{
- CachedPlan **planrefs = owner->planrefs;
- int np1 = owner->nplanrefs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->planrefarr),
+ (Datum) plan);
- for (i = np1; i >= 0; i--)
- {
- if (planrefs[i] == plan)
- {
- while (i < np1)
- {
- planrefs[i] = planrefs[i + 1];
- i++;
- }
- owner->nplanrefs = np1;
- return;
- }
- }
- elog(ERROR, "plancache reference %p is not owned by resource owner %s",
- plan, owner->name);
+ if (!res)
+ elog(ERROR, "plancache reference %p is not owned by resource owner %s",
+ plan, owner->name);
}
/*
@@ -1017,25 +965,7 @@ PrintPlanCacheLeakWarning(CachedPlan *plan)
void
ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ntupdescs < owner->maxtupdescs)
- return; /* nothing to do */
-
- if (owner->tupdescs == NULL)
- {
- newmax = 16;
- owner->tupdescs = (TupleDesc *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
- else
- {
- newmax = owner->maxtupdescs * 2;
- owner->tupdescs = (TupleDesc *)
- repalloc(owner->tupdescs, newmax * sizeof(TupleDesc));
- owner->maxtupdescs = newmax;
- }
+ ResourceArrayEnlarge(&(owner->tupdescarr));
}
/*
@@ -1046,9 +976,7 @@ ResourceOwnerEnlargeTupleDescs(ResourceOwner owner)
void
ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- Assert(owner->ntupdescs < owner->maxtupdescs);
- owner->tupdescs[owner->ntupdescs] = tupdesc;
- owner->ntupdescs++;
+ ResourceArrayAdd(&(owner->tupdescarr), (Datum) tupdesc);
}
/*
@@ -1057,25 +985,12 @@ ResourceOwnerRememberTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
void
ResourceOwnerForgetTupleDesc(ResourceOwner owner, TupleDesc tupdesc)
{
- TupleDesc *tupdescs = owner->tupdescs;
- int nt1 = owner->ntupdescs - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->tupdescarr),
+ (Datum) tupdesc);
- for (i = nt1; i >= 0; i--)
- {
- if (tupdescs[i] == tupdesc)
- {
- while (i < nt1)
- {
- tupdescs[i] = tupdescs[i + 1];
- i++;
- }
- owner->ntupdescs = nt1;
- return;
- }
- }
- elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
- tupdesc, owner->name);
+ if (!res)
+ elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
+ tupdesc, owner->name);
}
/*
@@ -1099,25 +1014,7 @@ PrintTupleDescLeakWarning(TupleDesc tupdesc)
void
ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nsnapshots < owner->maxsnapshots)
- return; /* nothing to do */
-
- if (owner->snapshots == NULL)
- {
- newmax = 16;
- owner->snapshots = (Snapshot *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
- else
- {
- newmax = owner->maxsnapshots * 2;
- owner->snapshots = (Snapshot *)
- repalloc(owner->snapshots, newmax * sizeof(Snapshot));
- owner->maxsnapshots = newmax;
- }
+ ResourceArrayEnlarge(&(owner->snapshotarr));
}
/*
@@ -1128,9 +1025,7 @@ ResourceOwnerEnlargeSnapshots(ResourceOwner owner)
void
ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Assert(owner->nsnapshots < owner->maxsnapshots);
- owner->snapshots[owner->nsnapshots] = snapshot;
- owner->nsnapshots++;
+ ResourceArrayAdd(&(owner->snapshotarr), (Datum) snapshot);
}
/*
@@ -1139,25 +1034,12 @@ ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot)
void
ResourceOwnerForgetSnapshot(ResourceOwner owner, Snapshot snapshot)
{
- Snapshot *snapshots = owner->snapshots;
- int ns1 = owner->nsnapshots - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->snapshotarr),
+ (Datum) snapshot);
- for (i = ns1; i >= 0; i--)
- {
- if (snapshots[i] == snapshot)
- {
- while (i < ns1)
- {
- snapshots[i] = snapshots[i + 1];
- i++;
- }
- owner->nsnapshots = ns1;
- return;
- }
- }
- elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
- snapshot, owner->name);
+ if (!res)
+ elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
+ snapshot, owner->name);
}
/*
@@ -1182,25 +1064,7 @@ PrintSnapshotLeakWarning(Snapshot snapshot)
void
ResourceOwnerEnlargeFiles(ResourceOwner owner)
{
- int newmax;
-
- if (owner->nfiles < owner->maxfiles)
- return; /* nothing to do */
-
- if (owner->files == NULL)
- {
- newmax = 16;
- owner->files = (File *)
- MemoryContextAlloc(TopMemoryContext, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
- else
- {
- newmax = owner->maxfiles * 2;
- owner->files = (File *)
- repalloc(owner->files, newmax * sizeof(File));
- owner->maxfiles = newmax;
- }
+ ResourceArrayEnlarge(&(owner->filearr));
}
/*
@@ -1211,9 +1075,7 @@ ResourceOwnerEnlargeFiles(ResourceOwner owner)
void
ResourceOwnerRememberFile(ResourceOwner owner, File file)
{
- Assert(owner->nfiles < owner->maxfiles);
- owner->files[owner->nfiles] = file;
- owner->nfiles++;
+ ResourceArrayAdd(&(owner->filearr), (Datum) file);
}
/*
@@ -1222,25 +1084,12 @@ ResourceOwnerRememberFile(ResourceOwner owner, File file)
void
ResourceOwnerForgetFile(ResourceOwner owner, File file)
{
- File *files = owner->files;
- int ns1 = owner->nfiles - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->filearr),
+ (Datum) file);
- for (i = ns1; i >= 0; i--)
- {
- if (files[i] == file)
- {
- while (i < ns1)
- {
- files[i] = files[i + 1];
- i++;
- }
- owner->nfiles = ns1;
- return;
- }
- }
- elog(ERROR, "temporery file %d is not owned by resource owner %s",
- file, owner->name);
+ if (!res)
+ elog(ERROR, "temporary file %d is not owned by resource owner %s",
+ file, owner->name);
}
@@ -1265,26 +1114,7 @@ PrintFileLeakWarning(File file)
void
ResourceOwnerEnlargeDSMs(ResourceOwner owner)
{
- int newmax;
-
- if (owner->ndsms < owner->maxdsms)
- return; /* nothing to do */
-
- if (owner->dsms == NULL)
- {
- newmax = 16;
- owner->dsms = (dsm_segment **)
- MemoryContextAlloc(TopMemoryContext,
- newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
- else
- {
- newmax = owner->maxdsms * 2;
- owner->dsms = (dsm_segment **)
- repalloc(owner->dsms, newmax * sizeof(dsm_segment *));
- owner->maxdsms = newmax;
- }
+ ResourceArrayEnlarge(&(owner->dsmarr));
}
/*
@@ -1295,9 +1125,7 @@ ResourceOwnerEnlargeDSMs(ResourceOwner owner)
void
ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
{
- Assert(owner->ndsms < owner->maxdsms);
- owner->dsms[owner->ndsms] = seg;
- owner->ndsms++;
+ ResourceArrayAdd(&(owner->dsmarr), (Datum) seg);
}
/*
@@ -1306,26 +1134,12 @@ ResourceOwnerRememberDSM(ResourceOwner owner, dsm_segment *seg)
void
ResourceOwnerForgetDSM(ResourceOwner owner, dsm_segment *seg)
{
- dsm_segment **dsms = owner->dsms;
- int ns1 = owner->ndsms - 1;
- int i;
+ bool res = ResourceArrayRemove(&(owner->dsmarr),
+ (Datum) seg);
- for (i = ns1; i >= 0; i--)
- {
- if (dsms[i] == seg)
- {
- while (i < ns1)
- {
- dsms[i] = dsms[i + 1];
- i++;
- }
- owner->ndsms = ns1;
- return;
- }
- }
- elog(ERROR,
- "dynamic shared memory segment %u is not owned by resource owner %s",
- dsm_segment_handle(seg), owner->name);
+ if (!res)
+ elog(ERROR, "dynamic shared memory segment %u is not owned by resource"
+ " owner %s", dsm_segment_handle(seg), owner->name);
}
resource-owner-optimization-v4-step2.patchtext/x-patchDownload
diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c
index 9ee654e..5a00a03 100644
--- a/src/backend/access/hash/hashfunc.c
+++ b/src/backend/access/hash/hashfunc.c
@@ -297,6 +297,9 @@ hashvarlena(PG_FUNCTION_ARGS)
* of 2. There is no need to do mod a prime (mod is sooo slow!).
* If you need less than 32 bits, use a bitmask.
*
+ * This procedure never fails. Its important. Some code notably ResourceOwner
+ * relies on this.
+ *
* Note: we could easily change this function to return a 64-bit hash value
* by using the final values of both b and c. b is perhaps a little less
* well mixed than c, however.
diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c
index 3330c8d..abed36c 100644
--- a/src/backend/utils/resowner/resowner.c
+++ b/src/backend/utils/resowner/resowner.c
@@ -37,29 +37,60 @@
* which should be called before corresponding ResourceOwnerRemember* calls
* (see below). Internally each type of resource is stored in separate
* ResourceArray.
+ *
+ * There are two major reasons for using ResourceArray instead of, say,
+ * regular C arrays.
+ *
+ * Firstly we would like to prevent code duplication. For instance
+ * ResourceArray provides generic Remember/Forget/Enlarge procedures, so
+ * corresponding ResourceOwner* procedures are just a typesafe wrappers for
+ * these procedures.
+ *
+ * Secondly ResourceArray must be more efficient than regular C array.
+ * Current implementation in general could be considered a hash table. It has
+ * O(1) complexity of both Remember and Forget procedures.
*/
typedef struct ResourceArray
{
Datum *itemsarr; /* buffer for storing values */
+ Datum invalidval; /* value that is considered invalid */
uint32 capacity; /* capacity of array */
uint32 nitems; /* how many items is stored in items array */
+ uint32 maxitems; /* precalculated RESARRAY_MAX_ITEMS(capacity) */
+ uint32 lastidx; /* index of last item returned by GetAny */
} ResourceArray;
/*
* This number is used as initial size of resource array. If given number of
* items is not enough, we double array size and reallocate memory.
+ *
+ * Should be power of two since we use (arrsize - 1) as mask for hash value.
+ *
*/
#define RESARRAY_INIT_SIZE 16
/*
+ * How many items could be stored in a resource array of given capacity. If
+ * this number is reached we need to resize an array to prevent hash collisions.
+ *
+ * This computation actually costs only two additions and one binary shift.
+ */
+#define RESARRAY_MAX_ITEMS(capacity) ((capacity)*3/4)
+
+/*
* Initialize ResourceArray
*/
static void
-ResourceArrayInit(ResourceArray * resarr)
+ResourceArrayInit(ResourceArray * resarr, Datum invalidval)
{
Assert(resarr->itemsarr == NULL);
Assert(resarr->capacity == 0);
Assert(resarr->nitems == 0);
+ Assert(resarr->maxitems == 0);
+ Assert(resarr->invalidval == 0);
+ Assert(resarr->lastidx == 0);
+
+ resarr->invalidval = invalidval;
}
/*
@@ -70,11 +101,24 @@ ResourceArrayInit(ResourceArray * resarr)
static void
ResourceArrayAdd(ResourceArray * resarr, Datum data)
{
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
+
+ Assert(resarr->maxitems > resarr->nitems);
Assert(resarr->capacity > 0);
Assert(resarr->itemsarr != NULL);
- Assert(resarr->nitems < resarr->capacity);
+ Assert(data != resarr->invalidval);
+
+ idx = hash_any((void *) &data, sizeof(data)) & mask;
+
+ while (true)
+ {
+ if (resarr->itemsarr[idx] == resarr->invalidval)
+ break;
+ idx = (idx + 1) & mask;
+ }
- resarr->itemsarr[resarr->nitems] = data;
+ resarr->itemsarr[idx] = data;
resarr->nitems++;
}
@@ -86,24 +130,24 @@ ResourceArrayAdd(ResourceArray * resarr, Datum data)
static bool
ResourceArrayRemove(ResourceArray * resarr, Datum data)
{
- int i,
- j,
- lastidx;
+ uint32 i;
+ Datum idx;
+ Datum mask = resarr->capacity - 1;
Assert(resarr->capacity > 0);
Assert(resarr->itemsarr != NULL);
+ Assert(data != resarr->invalidval);
- lastidx = ((int) resarr->nitems) - 1;
-
- for (i = lastidx; i >= 0; i--)
+ idx = hash_any((void *) &data, sizeof(data)) & mask;
+ for (i = 0; i < resarr->capacity; i++)
{
- if (resarr->itemsarr[i] == data)
+ if (resarr->itemsarr[idx] == data)
{
- for (j = i; j < lastidx; j++)
- resarr->itemsarr[j] = resarr->itemsarr[j + 1];
+ resarr->itemsarr[idx] = resarr->invalidval;
resarr->nitems--;
return true;
}
+ idx = (idx + 1) & mask;
}
return false;
@@ -119,27 +163,33 @@ static void
ResourceArrayEnlarge(ResourceArray * resarr)
{
uint32 i,
- oldcap,
- oldnitems;
+ oldcap;
Datum *olditemsarr;
- if (resarr->nitems < resarr->capacity)
+ if (resarr->nitems < resarr->maxitems)
return; /* nothing to do */
olditemsarr = resarr->itemsarr;
oldcap = resarr->capacity;
- oldnitems = resarr->nitems;
resarr->capacity = oldcap > 0 ? oldcap * 2 : RESARRAY_INIT_SIZE;
resarr->itemsarr = (Datum *)
MemoryContextAlloc(TopMemoryContext,
resarr->capacity * sizeof(Datum));
+ resarr->maxitems = RESARRAY_MAX_ITEMS(resarr->capacity);
resarr->nitems = 0;
+ for (i = 0; i < resarr->capacity; i++)
+ resarr->itemsarr[i] = resarr->invalidval;
+
if (olditemsarr != NULL)
{
- for (i = 0; i < oldnitems; i++)
- ResourceArrayAdd(resarr, olditemsarr[i]);
+ while (oldcap > 0)
+ {
+ oldcap--;
+ if (olditemsarr[oldcap] != resarr->invalidval)
+ ResourceArrayAdd(resarr, olditemsarr[oldcap]);
+ }
pfree(olditemsarr);
}
}
@@ -152,12 +202,24 @@ ResourceArrayEnlarge(ResourceArray * resarr)
static bool
ResourceArrayGetAny(ResourceArray * resarr, Datum *out)
{
+ uint32 mask;
+
if (resarr->nitems == 0)
return false;
Assert(resarr->capacity > 0);
+ mask = resarr->capacity - 1;
+
+ for (;;)
+ {
+ resarr->lastidx = resarr->lastidx & mask;
+ if (resarr->itemsarr[resarr->lastidx] != resarr->invalidval)
+ break;
+
+ resarr->lastidx++;
+ }
- *out = resarr->itemsarr[resarr->nitems - 1];
+ *out = resarr->itemsarr[resarr->lastidx];
return true;
}
@@ -170,6 +232,7 @@ ResourceArrayFree(ResourceArray * resarr)
Assert(resarr->nitems == 0);
resarr->capacity = 0;
+ resarr->maxitems = 0;
if (!resarr->itemsarr)
return;
@@ -287,15 +350,15 @@ ResourceOwnerCreate(ResourceOwner parent, const char *name)
parent->firstchild = owner;
}
- ResourceArrayInit(&(owner->catrefarr));
- ResourceArrayInit(&(owner->catlistrefarr));
- ResourceArrayInit(&(owner->relrefarr));
- ResourceArrayInit(&(owner->planrefarr));
- ResourceArrayInit(&(owner->tupdescarr));
- ResourceArrayInit(&(owner->snapshotarr));
- ResourceArrayInit(&(owner->dsmarr));
- ResourceArrayInit(&(owner->bufferarr));
- ResourceArrayInit(&(owner->filearr));
+ ResourceArrayInit(&(owner->catrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->catlistrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->relrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->planrefarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->tupdescarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->snapshotarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->dsmarr), (Datum) (NULL));
+ ResourceArrayInit(&(owner->bufferarr), (Datum) (InvalidBuffer));
+ ResourceArrayInit(&(owner->filearr), (Datum) (-1));
return owner;
}
Hello.
I have applied this patch and can confirm ~10% speedup by this patch in presence of big amount of inherited tables.
Test case was as suggested by Aleksander: create 1000 inherited tables, no constraints, insert a row in each one, and issue single row queries over this table.
Xeon-based server 12C/24T, 50 connections, 30-min average:
TPS, no patch: 393 tps
TPS, with patch: 441 tps
The same setup but with single table with 1000 rows give performance about 188_000 tps, so overall speed of requests over a inherited table in this scenario is quite pathological (probably this is expected because database just execute 1000 selects to each inherited table). I've also tried to set range constraints for all inherited tables, so only one table was affected by query, but planning time increased a lot and total tps was again about 500 tps.
Also attaching two flame graphs measured during tests. It’s clearly visible that PortalDrop takes x4 less time after patch.
Stas.
Attachments:
inher-patched.svg.gzapplication/x-gzip; name=inher-patched.svg.gzDownload
�p5�V inher-patched-2.svg ���{������W�27����>P(X���c����r4��&����^�� ������=��Bd
PGR�v���D�����������z�m�����_^�e|���b�.��}����p��~��������������E��v�������}������o�����\���?-���y��?^-������7o�by�n���*�w�U�^�_oz��r��������`W��������1��]���k��J2y�8���b���f�i^����W�������/��E���,�+�������W�x�K~�����]/dY��1�����?_|�-v���m������b������_.���?��O��4w�����{s]�`��mU���1b���9T�m��uc
-�X�e�V�Y7�vs�����?�|,v�mY/���.o���?��wp�eQ����r�,6`�k����p���O���`k0G��#7������9���W���������������K�V������k���k3��������g\���\4OGX�M���Y�5�����i��o{w K-�7\�:�������|����Y7���������o����)�/�K���k����^�\���������?~��W����E�X�M������;^�]7�%��fuW���&���������X�+n���{��b}X�v������z[���������%W��7��n��m��/���������5��'���zG���-��������cq������[^�5���Mm���~��O?������p������r��9�R���%��|m}wx�O�������!�o�}�g�-��TlO%������~��Z|f.`�~����� ^a��������<������cn�}�O?y�����e�^�����n�}Y���/�������+|$|���%��-\g���_.������x��R>-��X�_����wR.�U���cyS���+c�����:�����q����s{��7p��
���c��=|�p�4M�^W�y����li~��pg5^��
_��2�b���}�r[�o��/6�}��9>���_7[6����3~����*�S���X���8zJ��r�z���w�6�'8������{���v���
��>[�js�����U���>b�
.q���_}f����������_N������7���>!��\���������O�zVt��F���c~��?to�o{�q����l�<���?o���.�g��`��]5�f[���v������w�v\^;����3���?m�f�v���������,��y���������y:����x���3V��i?���a�����[���h^U�/�8����y��\�;e]����J8
�'������^��7�����{�ft����*���<���3s/��#����n�[4w����������a������L��<,~���a�7�����i���>����U���r�_�����o�B88�o���^w�7�k�������W���x���t�p��9_�~dw<��}x=�����������/���C����G��.���O�>�
.�O��O��y#�����Z.[�������v�����`=��yU�A5v|�o��)jhtP�jh�������.����S���F��*Z����8.��g�5x�����>�~�<F�
�{�'~y�p������m���7����`�}�U����?�^���uX,n?zs����dY|���c��h`{j��
W��r�;�<�0X
�
1:��G��{+����Xe��p��*���v!}h�����������5����o��!�X�?.����y1�b�o�������������4�����v�]��m�z`�������1�����/�m��W?:^Y<�_i��Y{��eO�����"p�|�k�o |38o�K��������v�����05�i�����]����e��>����f��h��6P[����q��#��=�&/n_����
KS����X�6
<':<,}
>���'����V�����a�d���<��]�������H��g�d��o_���U/~��[��� �9Tv����b�����m��������f
�[�.io�����>����[���3��� x�X��lH������������l�)�^9�y%�������}�a��n�C�����z���=0z}����7��������?�xsu��J`l���v��t��!��t��WgC�'��I��x�6���\i�:�vpX��Fkm�,��Y��>h��F�H�k~�,l�k��9)F�����~}��+��/:\gCW��C�i���au��y������`�o���o;�b�����v�V4������i�����,���\� ������f��^�~��>�����?�e��#��A����}���X����}i��WUy[>����Z��]Azx(���������d���O����k�z�L�z����!86���w��J�Xy��|~��
�/}������*��r��C�z��o�}�sI_�'�Cy�������
�/oo��>������j��G6��g~��5���qW����������p�2W�$o�m��E)�+3�3>k��c.��h��`s6we��������*��r�����w�#�W�a����wUy��W��.��������V��fx��j|��S�\1� =�����s\� M�����������D������_P,����
����y��5���F~�0�.�������/�,K����_��;���M������<�����?�lQ�������n����W\�Q�?__�w�7X�~�]���������>>/�v.{�w4�'����+�7�����PmX��g@uv}x6�}p���!{��
���8ms�O�M����?� ��'g�������=����V�u�$����������n6�J������M+�p{0�/�8ip�|���y���|�����*�^�}�=����������������:�M6O��_��?ly<�w�[KC_�s�t���*8$���\�p�v����'�'���.Q����������_��\-����V����}X��|��D�������/�������P����]��]S�� �����{���)�����[l�_�����t�^�iw�{8v��y�1��Z��r�#��r���<�����,1�0&o��pW����i������o�W�������C�e�f4��I=;[������������x�g������v����.����h������~����x���_;���t6����h<
���o���?��������"�,��\�������-�s���<��W���^a������c���W���s?lpG�
��9�������<R�;����_�h����
�-����c�� ����:���]�?�����Tm_��3P���a�3
���m ���n�^o�+����k��%|������J�o��f���Z��^
�:����cc�1�{�s^�\��WL�t|I�I�XL�}���kv �����/^�F|�_����p�/�x���y�x��g��=w`�g����bi6����NM�����,�s<.{�����<�/]&3\��"|i��g�h�c�F�~.u ��4_,n�� ?��
.�@�"�{���m^���4��i�e&i���%?of���r4���H&p+|��z2�ro���:b�T{!�.��m���{�����e�����d{����`F>�#g;f�NE�1���v�i�Ri��>E�b����4�|}��h�!^B�V7�,u�x�F��mV����&��vn$8��j�u(�L:��cC
�!9<_�>^*����<b"���<��I�>��nGHt��%�|_>������\���k�)����f"#�����N��2fmn�*���'\�l��d)2�h����i�j����t��zB�~�&�7����!��<�g1��w�\����Y�xI��+%��=�e�]�-(�X��S��m�>��I��I��oL�F<QJ����(�������S�_W��W2�C��s92�l��TIoC=iC c&#Ir��q��]�+������ Z�]�XW�5�e�.����c]N��|x[\�l��Iu(+3���m�����*?�=,Q�����r4f��x�,���a��'4�K�n������.�����b���=/�UY�dQ��NM�`�Q�N���u�\�}�����IDZE�t�f���.��.�3=K2�G��Y�$�����L�I�3�j�K�fB^|��m����`G1kG����z�m��uI���+)����}�,��<V7u^C� ���a@�rK����p)���Y��K-BqK?���T����e��~�y���y�q�8S�a\.U������1!m�<���\fK��b�<o�������JpB,��x�n���3|�!��'.S,�����7=D�����!�_Al����z�j���N�&�ZTWg6�:!{�����hv��������,��Rw�-��\i� e�?�_g�/e8�RU<��q����������|��Y��(���H-Y�����45U�1f���y?��?�{<?�N��M\9})}�"�L��x�c�F�02z��~~�X�����v��m~|:��T����an4�F�;9�Bu�_���o�ti�G�T�x�tN>����9�Wy�z�3�p�"^��E�����&��|!�I��40W4���;5��>���2�T�s �3�$Lf�8�|���|]��G�) 3J�������D��tb
H�T�(���������=��� �q�d����@�����A��,�?��r��fS�l���������T �����CN�DF�T�T�\��������Te����2��<��Fi�t�ra;��RN&AL�b)E�0��P�J���o�l��U�t����.�����%y�&7:<�i'�R�_���'�(g�j{�!�_/^�� v8bWX$Q���GLS��l�� L���R�K�!�\�=#�S�$��H��JD�eI(�����������HS������<�Rp�$+��R�P�5�BO[/���{�?ZRp�7�R����@����J�Io�����W��13��&Wc��_Z����2�C ��Bx���1�^�R�����B<Ku!6�(�,bJG�t:�L/3J]��!�xA:��\i�T
�A��2����Q���|1#���.��`��rhP5�7�1������k);t{�E�
s��.�$Q]�3^�S0�P��zH��BGL���M�We��3��^��� �����G���u1�����.o�!�9�����V��UGIF�3�0S�N��M�u4��k[1L�03�*�r1�u52�Cm$��S�r���<�yc���ZTEv���7��I���6� ���F���n�v-*p/|��0l�(�Z�
|��L�')�%-�4��hDf��y�����5d��:�{���G����t����eu�b���P�e��kII|:�w�f8�7��PV�i��R'��
'���I!m�{��1a %��F�l)y(��z[�����|��D�B���8eE��0X��N��#��"��t�L6[�I$�3���c����L�\
�:L�m������X�S��H *T N!��C9������6wh&!k;�DT\I,YJ��h�!$3����9�i����,���$/��`�:$��
x�������L�7t��Y�7( �j&c���">.��Q��D�%B�����4�\��pA+��'���1�1��9�Hc�����N�C����@*##ID�b�C��i l�=�f����M���W,��p��`�
,�� I�x���s�1#��*��TJ�������%�6�����)cv���g�"��0�Sj��/���� W�c^���^C�R�v������,��$'��R;,@P�:�z�~�s:����4�X�`4Lt_�`��9r�5
{ l�b����<�NMo�� e�mp��)�r����<.��n���p�`$�r�8�����<q*\������������77�h�{_�t�P�:��+KU�vvb �,�L- #��i}���fs�����W�1U��, �6e)q�>U �����!|����;|�G�Q�4c�T_"����$�~�� E�d��r�o�z��f����
��O�'d�iL���%[t�xs���>�nB"���q9��
�H��[����P�7�k;d���/:��7�\�1�O�L�r��G;{<�(c�K�(�4�l�t�p_������GG�p�]�������3�#�b*�E+���4����
���������V:�o&� J"I��_j����.�,DCp��O�B,�DJ���)�D��5-IX�67s5��K�9���C�V������7�};����hK14�#'�,J8u� �
LY�Np4��������zlj���8��:�TB� Le0�8�S �lU���#���M ����U��
%����t�F�l���b)N��V�*��"�g
�Y����:Vz)�P7�N77eU{�&�� �7�(fP�� �8��B������F\�-*�9��t�z �3b�W���v���^"a�����i����'��<���6�L�����+A��B���C^�[�����)�+G/wfL9cd�j��4���E��������q1�u<�hW��X����|���~8+i�
���/\�_V��� ��/v�����@���� Q�����>�{�Q�v6�/�b���ptG*#�H`��t(�{��1�`���<U�d^u�>����UU<Q��_~���fg�or��o����5����"J��l�������`��[��u�z��I1:=g5)�sF����b��3��X�p�c�*��ne�R
1>�'���pjxaX������[O�j���QI$c�]��u��&���
�t &���(��������uQ=L���N��!i��p����S=�c��=udg����zD�=E���t)u(s���N�
�U�/���ciW�?���f$T�:���`��f������V�c���(&�R�����T^�������Yn�M��gyC[�y�c�J3*V=�K���u0�4���n�f� �
g�'fv�t�G��i�5��82��R��J3�>�G�s��c�+���B���<� ��p�_�B���;�w��|��!Y�ak�W���G���2��b��u^jni�8�� �)F$��<�P� p�/�� ,Z��#�cJMV�B����{���#b�e�ww�f}x����r\n�N�dK1�?���IiX�4y(8��_�/��x��(��W����D��1��U*�
��iJK���Q�z��rl�KoG�2����!I�Q�%`����b��C#Cd%(������q'�`V����Hk#*`�������]N��L�i�>Ga��
��4J��FIp�MN�A��4^�TdKL����V(�����m�*m���Lb�v�,�i4Ve����f �gX�x�{�g)��t��Yz�m�>���fF��<#R�`=��*;���������d�C�"ZeSc2�����Q�������<���g��`����W8�/��DH8ur�!��LH
1�]P�S�Oy��.0f�YM2��x)"
hh�T������_�y~Sw���*�~�Q�d����Fv��p%i� ���{,G���{+s�V��Kj �`&z�.d�j6��v �c��C��8��P�1�*�2�����/�7\�6��cWj��g��B�H&�Z���G�O��a�R�J�k��b3����2p`N�[H����t�����GY6{B"7�p4%&AXH�1*-���P��w���[Cx�||��
)�,"������i�c���;U����D�|zh!�T(��2�r��M���qE��$��Vz��1)I6I���P�����*!�9�7x<�c��+4d���wz*��Qm'���� ����<s�M� ETT�z��^ qdnx�:�t,W�q�d`q��|h�YA-�J?4����[�4{(��.Y� �!�T��e��Q f4�ZwE�D�;�>�`��}76G��!�J�/E^~\>���Xn����y[F�*B��$���[�� ��]��Q��/����G��9f�l���(�����,������S�Cr�>��9���9���\~@�N��A�14��\;Z�����Z����9MKazJ�h�AV%C9/1H��;�����dD7r��y�*F�#It=*��l�����{< ��O+�9t; PUQ������� ��}����>�bZ��n���y1no��g��$1:W*����8��G�r�'��IRQ#M��
��8���j6�1��5�8�� Q����ag��p������6�<c%��.)c%��GV?��_�p���R=R�N�QMpi5^�9�,�<�v��#�+m}��u_J�1�8~G#���1>�x���7�V'�Ge�c4�yJ��N�C����[�N�z3U�Bs��2�v�������f�c5��3�&c��R��,Fx�v�i���}K��=`wG�"t����[��a�=�p��$�'�~<����g�������(�h�O�P]��G0��,�
([�IS�eN���F���,���,���f�n��|B�b�I |��`E���s+X�������z���QP���N�$5����n�5s�!���G�
x(o������������?��f�����>�,����I{Yk��G�$R7��l�P���H�y!��#��eF���(--m �N�����p:���n�9S�yt�� �c��$�WE��
S�)2� ��������&?T9��������}�3�ZM��������RHp:����@l�+�+����Vg��`��������|������a������s*,�-Q?�]��'�����L*"���>� wr�-�c^�7SN>�������*3���>�������a�u����l; Y�lh�\���?��r*���N{� lk���40�)h��U��� ������_��9UE2�"�i_��IS8���������%}����y�_���]���*%"u�ZN��p*&28��d�26q�x������1� �L5�!�Ly�����S+�}�F��n
���<� ��g��ur��4#N�e\�a� �L7��c���t�M��$F���j���P�L�=�U��Lh%l�>2�$*KF�J9��2���m�M�x��kp5��2�Q�>&J�jX�Y
�e�|3_�����(�.�yz����|3(���4N[��#JP���s��86L
���*�<v�B��at�Y�G��P )G�8G�
�f�SJ���~]T���-�{�,��W3����}q���;>���]7�5��'�����c�����#���-�����&��v9S�=A�=�\�G��6��HQY��eqW6 `V��S�i���c �����B
K6����@8�KFh� ����O����&�t�B[ ���m*@7F�����PJ�31�B��eH��I��Jii���0����3U�q��M+&����%��H�p�HG�he>���2���H
m��Q.?�rOKK�=�
N���(#��dj�B�M/���vfO63W}�h����L+c'c4,�����'���-QG|����w��!3�#�8#&�vL����H1"�(��R�) b.���:T��|�J��*%���*e��FNe.���q(gc7���y^�M,q�4��7��cF\���&����������t��U �d��X0�h�E���G�85,g\>���SY=��C��0<(�A�*b�q�h~�TO#4t��%���f���=z����@�F^��D�����U�c���D��O�)�����������}���k�\f
a:����t0��#��|�(w�L%��4�B0v�$f����j���78����"bG�9mI���=�O=� ��lp,���[Hy�G��}��)��)���$�D�"�{���W����rX��O;B���8����8"��z��Q�
W������������-b2U9��Z�cs���c��c��@��zUm��L�F�[9Gr1�{Q�H'�kH�$�� �I������ ���w������!�`�1Hu���#A�j`���_%F�(�Os���0�X@�}��,�:!�0��D-& �B�OI>K����9��
}h��X�`F�nK�D7��U��}��L����� ���*&#�F�����4�67�zQ���E��n
��Sf���2;.$��Y�e_*��<��_~s�y�{~_j� ���4�����`B��[��[||8%��W727�������dL ,�������rm��N�y%��
��U���[GL+j�����6T�����i)X���.��4[��$���� J����c`�kir�Y����T�6k����a4@�PP�f����e����H�)�'D�
,������zb����O�5JY�NU���1�m��R�/���Oi�]@�k�m�����Cn��~(o���DjW'%B�%iu*�\&�q;z�2#G���D$h����,�������d�Sj�� [����[&� ��+�p�}Qm�k3���zS7�=��< �,L����K�39��?CB�`��C�����
���,���������,E �R��\KG��p�-KlMcX"���:J�q:\HYR� JD�}cJ���,�v��0���jFf'��x
��Pl���6o�U�������xd��0���J:qAf=�H "U�-�P������:�~�W�}�T�f�����$�V�|��U�����oq"����M�����\.?a����^w�&���RJ=��r�L`A��uf�L"������ g}V����bs�j�|s<LSIds3��]��@n��}����y�\9����Q��=Rv�� ���r�&G1����THa�#D�� ��0 Y�zn���CI�g���o���b=�U�rg��$��rf��0�f�.��u��J��1���ZBJf����@;3}5hP��O�Y���y]����1U0J�(��)����_�@������[�7!����va��d�H��xI��T���`h��K;2�Yfp�,(4 +���$�ynDLC��Q�uW��^z(�)g�mr�?��-`w<:��^�w��%����b���`��)u�����pT���~��g��!{<�jb�D CiA�p(������]����������R�M;$�����`�������a8-W8y����Ki��o}P&�`uCb`��g��f�bK�c �)l�^���O��9�U%�,�~���U�����]+?|��>Y�&&��H�m{�Z��*zx`�������b)� 6�a��:�O���O;��f�����{J����|7Y����"ZG���2 ���*������9���2�Y��&���q����]~T�C��v���]�o~�oQ�L�F�����B
��1b�!��m�T}%��!1y^7����u]�����O�^���gN���el�����g���gO�����!���g*y���/y0-��%yWax���t�gS�&���KA���&e:��M�?T����������B��T�J�S�4�Y��~W���-�"�&����GS�S�D��gT}C�;Z� �w��5�D���xC�j��Ii}��9���4�HqQ������;��Q�s��cj7���$�C��$��K���L/�`�qZ�jJH��
���bp���9R����&%Cm�6�,�������Z��U�M�U�K��"�1��$���tJ�Ouf� �uv�n\�8Pu4`��������H"��iew��+�B���f_5
���k�u[����f��&mZ%�H��2������OG��Q� ���2V&+�)�d�Z�`�j��l���e��m��F/�4-��8������pL���H�v��������TY�F���d2
��&'��J�)���3�mq<�{-���vR/�.������L����9�.{7��]��� �x�� k���
�C�2���Xm���Oe�3��9�e
rOi
4���B�j��G8k���WL?R�|M��s0�����112�J.E@���������������.�q�G����d)|1��.���E���:�`}z�w$b��I%Mp71V��%�CI�����|u���lqdO������R���Gq�ib��1��������A%C�,�h�.NKr�J��N�`=�(�l�pV��T����}��E�b�zh����9 �lk��jI��b��P0�g��I�,��,�zDLt=
el����)���%��gn�������h<Yj
��q����+Ob����f5� >�$*+%I(�;��mq�.}�����98��&Tv�V��4p_��d\���88���z�f�e65U��k���_~M
��o
au^�N~�;N�#_�AU����
�q�� 7���)�������7�3�E'$s��%� �Y���L���!����I��j2��bB4%��� �����G�w[R�y��Jz�L���O0Yd�)�~I0u�����6yU���*=kZ�kBE�"�%(:�0�Q�N{��\�d���fD���D1 ��Zb7��y�1�����r�� ��,���R�9]�7 ��?��_����A�KH�z�TO^S�<���c���f-&������Xn�@�������=�?GI#������&�B�i�o��3����
1�4������f��6�����7��C�z�S�I�p�_�]EiIR
��D�RQ����r��=A���=FWM�,bH�A��).(��-�b{�5��o���)�3l�*��e���vd���Nb}y$2�?�2����nF���[�n���D��X�@H1�$�G��;##���6����[9�g�?��IdK�C��o�es��9&���: o� e$�h�$�����\�������"R/f���)#�,B�QZ�3����P7�9Z��=�R�
���[4l6����t�~A�������|��I���?l�����}��=�L����f�4�&)�
��<����2��dg��8�2W���b�<�
5�SO5��2gi�?��.p��1�k5��s_�I���a01���$v�� ���.�Y�$��
����Pn��.��y�����HI&�:i���]��w�z �$7�HjRq��-�*�,9�JL�� �����S?P/��rd��M=�q��KK� ��3]W�z����v��N��0k��r��t��v@�S�:Z�b��i�r!m��[mB�����D�&[3`��z����x���[X�r6FG����B3�F)��@�, r���mn3�lB"K?�x5&%��f�}6)����p2e@X(�����>>6yS���M��|� �?Q����&f��02�T<����G�� 6�n���c|�^��?�5)��RA,c��b�� h�7G��C���h
?E3�&���@P��>�W�
`u�Z�����"N�0i�]fQ'&i�[�Q�B��WU����m�������E1�r�'u�Jhe=�p�QO7����������y�K�7�������D��h�M��@��Q��H��D(-���y�7H���j�1c�y��~a�~Q�L��"R�Vk���������
�^L�X�N`��M� ��f"'���@��C'V��0t�o*c:*��pz��i�i���Fu��|�4^���NV�IjPe��F@�qUPw{^6���0j�Q6�J���}J<���C�M�&����8!� i����a��'��8�G���G��y�Dd)����"��\�C�!�M<���^�^��{)��$�U�*f�pR7(�P@.~x�S��������(�Q�h�&'O��6FW�Q���E��pb���f
�q��d��P�Y��pjFr����+�e�C���4�?��,fc��5���� 7r!�]�u��*��V���kO�%��� #��8.���z��/fxA7��������J6>9R& ��"A�^�p�L�W-�%�
*���'���#�N�WF�4/��`r�o�e�� d��;g����e��p�/���s~8����8�/������� ��: ��QFS���SU��(|���U�j��6/�[�)�^0�tx�Q�������t��e�p,kx�a����0���h��3+��������F�_��B���N
��H�x���X[����3����zh�;��s����&=�A�:5�i�*9��P���t�>�k������(�%V]��($[�sR�GBt�P*���x������msnv�#� ht����`jg#��wM����T��~���e�&cH�����J}&���UiT������������1�J 1������}����������)1��i2Ll�M/,��/j"�����nL,�IdEf�J����GIn����P<x�Yw�]��e�V,h�ma������v�?=��1[Z�<qX2g�[�ni�L�$�E*<-)&B�����^��I�����|_�����n^�k�t��&��q>�Z��+�!�P ���E3����Y
n$bRe��q0L�:7��S�����d�3
����� 2L��F�y������W-R`�����^o4p;��N���
�r��;�Zh�_��8
��h���T�Gs,�];g<U63��M4��$�;L����y��SeJR�>v���xZ"'�(��=n-�����/dh��z��SSu.d\~G�
���_�W��r�z��!-��$�"�>� ���������S�
Zu�;5Es@�=:-��VtN�Gt9��%�(���z�Iy8X���A^K�us,�m��1����9�.rJ>-o�
�%�(W����U�q�&����}�eoX�G���%b'�ejz�e�QN�lb��Pi[9��������
rK8!���&]6����-�FM�8�C�K�d*��YS���P�P ����w~��C��w�/�A�H�>J&�N��|)w2/b$�C�d�(�Bix,����*=��uY����
������Js�%����b�X��E�`��b����n��(%�����c���$|L�Ci���6=Q�>_��������<K�+��
������[A��GN�km�wF*����#��D���m@!��8A�����yx4�aU��pVv4����q�Fl���,��G��f���e�Y�� �E�b=�F�a'nU�%�!�������fV�9 .`�}���7�]���������0F��>9MR��p~�`������u�lvFh��Sn��}��P����%����O.~G������u)s�%p�i'��d�##M5#��Z@����me��'Cg=O���e] L�M�f����L{�����L��G������n�
M3�@6��,bM]/�4��X�N�b_�=�����3������U�>B!5�Q�t'���^ �A�,P~�"��{���2{`���e�9l�P�F��q)��i��U����B%�%#W�����2�����]Q N��MD$Q���zHl�@�!w1����{��gN���p\�� �=Y_\8�a���v
%��8�I�&��5�A���b�Y:+
1�8Y�~��"��?�G0��l�����Oo��������:���9q BB�7ZA(���@�e���N���^J(����I<L�i�"�����i��-�����Y�����A,��!�A&���J��uJ����8?��s5Nz��6Q�VJ��X��� ����>������%=�4e�UR���������K���B�d�#["�*L�=�r}�%0Nm&�8��Cy�������b� �)��t�d
���x��>�0�Lii'Q ��3�2���$v���������X*
���r��8~<�F�M�������''���4�q���&�.w0
y4��� j��*��!��s��T�X �����bOZ�7������n��CVG#����I��)���Q����]T6]�v�_r��-��)�T��]�<