Small catcache optimization
Hi,
Heikki's catcache rehashing stuff reminded me that I'd posted an
optimization to catcache (20121220153555.GH4303@awork2.anarazel.de) some
time back which I didn't have energy to pursue at that point.
I've brushed the patch up a bit and verified it still gives be a
performance improvement. It's still about 2% in a readonly pgbench on my
elderly laptop.
There's basically two tricks in the patch:
1) Don't always copy the cache's ScanKey to the stack. Instead pass
an array of arguments around. That get's rid of a good amount of
memcpy()ing in the common, cached case.
2) If we have to memcpy() because we need to pass a ScanKey to
systable_*, copy only cache->cc_nkey * sizeof(ScanKeyData) instead of
always copying the maximum size.
I'd be nicer to get rid of the mostly copied HeapKeyTestArg, but I don't
immediately see how.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
0001-Improve-catalog-cache-lookup-performance.patchtext/x-patch; charset=us-asciiDownload
>From df40dab17e7582ab597214d3c7fcefd953dcc9f7 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Thu, 5 Sep 2013 20:30:44 +0200
Subject: [PATCH] Improve catalog cache lookup performance.
Do so by not copying the entire ScanKey upon entering SearchCatCache,
SearchCatCacheList. Instead perform operations like hashing on a smaller array
containing only the passed in Datums.
When a lookup into the underlying relations need to happen, copy the Datums
into a local copy of the ScanKey as before, but use a smaller memcpy() if
possible.
---
src/backend/utils/cache/catcache.c | 120 ++++++++++++++++++++-----------------
src/include/access/valid.h | 56 +++++++++++++++++
2 files changed, 122 insertions(+), 54 deletions(-)
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index c467f11..fb7eaa2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -74,7 +74,7 @@ static CatCacheHeader *CacheHdr = NULL;
static uint32 CatalogCacheComputeHashValue(CatCache *cache, int nkeys,
- ScanKey cur_skey);
+ Datum *argument);
static uint32 CatalogCacheComputeTupleHashValue(CatCache *cache,
HeapTuple tuple);
@@ -174,7 +174,7 @@ GetCCHashEqFuncs(Oid keytype, PGFunction *hashfunc, RegProcedure *eqfunc)
* Compute the hash value associated with a given set of lookup keys
*/
static uint32
-CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey)
+CatalogCacheComputeHashValue(CatCache *cache, int nkeys, Datum *argument)
{
uint32 hashValue = 0;
uint32 oneHash;
@@ -189,28 +189,28 @@ CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey)
case 4:
oneHash =
DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[3],
- cur_skey[3].sk_argument));
+ argument[3]));
hashValue ^= oneHash << 24;
hashValue ^= oneHash >> 8;
/* FALLTHROUGH */
case 3:
oneHash =
DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[2],
- cur_skey[2].sk_argument));
+ argument[2]));
hashValue ^= oneHash << 16;
hashValue ^= oneHash >> 16;
/* FALLTHROUGH */
case 2:
oneHash =
DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[1],
- cur_skey[1].sk_argument));
+ argument[1]));
hashValue ^= oneHash << 8;
hashValue ^= oneHash >> 24;
/* FALLTHROUGH */
case 1:
oneHash =
DatumGetUInt32(DirectFunctionCall1(cache->cc_hashfunc[0],
- cur_skey[0].sk_argument));
+ argument[0]));
hashValue ^= oneHash;
break;
default:
@@ -229,17 +229,14 @@ CatalogCacheComputeHashValue(CatCache *cache, int nkeys, ScanKey cur_skey)
static uint32
CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Datum arguments[CATCACHE_MAXKEYS];
bool isNull = false;
- /* Copy pre-initialized overhead data for scankey */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
-
/* Now extract key fields from tuple, insert into scankey */
switch (cache->cc_nkeys)
{
case 4:
- cur_skey[3].sk_argument =
+ arguments[3] =
(cache->cc_key[3] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
@@ -249,7 +246,7 @@ CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple)
Assert(!isNull);
/* FALLTHROUGH */
case 3:
- cur_skey[2].sk_argument =
+ arguments[2] =
(cache->cc_key[2] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
@@ -259,7 +256,7 @@ CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple)
Assert(!isNull);
/* FALLTHROUGH */
case 2:
- cur_skey[1].sk_argument =
+ arguments[1] =
(cache->cc_key[1] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
@@ -269,7 +266,7 @@ CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple)
Assert(!isNull);
/* FALLTHROUGH */
case 1:
- cur_skey[0].sk_argument =
+ arguments[0] =
(cache->cc_key[0] == ObjectIdAttributeNumber)
? ObjectIdGetDatum(HeapTupleGetOid(tuple))
: fastgetattr(tuple,
@@ -283,7 +280,7 @@ CatalogCacheComputeTupleHashValue(CatCache *cache, HeapTuple tuple)
break;
}
- return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
+ return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, arguments);
}
@@ -1096,6 +1093,7 @@ SearchCatCache(CatCache *cache,
Datum v4)
{
ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Datum arguments[CATCACHE_MAXKEYS];
uint32 hashValue;
Index hashIndex;
dlist_iter iter;
@@ -1118,19 +1116,16 @@ SearchCatCache(CatCache *cache,
cache->cc_searches++;
#endif
- /*
- * initialize the search key information
- */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
- cur_skey[0].sk_argument = v1;
- cur_skey[1].sk_argument = v2;
- cur_skey[2].sk_argument = v3;
- cur_skey[3].sk_argument = v4;
+ /* Initialize local parameter array */
+ arguments[0] = v1;
+ arguments[1] = v2;
+ arguments[2] = v3;
+ arguments[3] = v4;
/*
* find the hash bucket in which to look for the tuple
*/
- hashValue = CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
+ hashValue = CatalogCacheComputeHashValue(cache, cache->cc_nkeys, arguments);
hashIndex = HASH_INDEX(hashValue, cache->cc_nbuckets);
/*
@@ -1155,11 +1150,12 @@ SearchCatCache(CatCache *cache,
/*
* see if the cached tuple matches our key.
*/
- HeapKeyTest(&ct->tuple,
- cache->cc_tupdesc,
- cache->cc_nkeys,
- cur_skey,
- res);
+ HeapKeyTestArg(&ct->tuple,
+ cache->cc_tupdesc,
+ cache->cc_nkeys,
+ cache->cc_skey,
+ arguments,
+ res);
if (!res)
continue;
@@ -1204,6 +1200,16 @@ SearchCatCache(CatCache *cache,
}
/*
+ * Ok, need to make a lookup in the relation, copy the scankey and fill out
+ * any per-call fields.
+ */
+ memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * cache->cc_nkeys);
+ cur_skey[0].sk_argument = v1;
+ cur_skey[1].sk_argument = v2;
+ cur_skey[2].sk_argument = v3;
+ cur_skey[3].sk_argument = v4;
+
+ /*
* Tuple was not found in cache, so we have to try to retrieve it directly
* from the relation. If found, we will add it to the cache; if not
* found, we will add a negative cache entry instead.
@@ -1341,7 +1347,7 @@ GetCatCacheHashValue(CatCache *cache,
Datum v3,
Datum v4)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Datum arguments[CATCACHE_MAXKEYS];
/*
* one-time startup overhead for each cache
@@ -1349,19 +1355,16 @@ GetCatCacheHashValue(CatCache *cache,
if (cache->cc_tupdesc == NULL)
CatalogCacheInitializeCache(cache);
- /*
- * initialize the search key information
- */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
- cur_skey[0].sk_argument = v1;
- cur_skey[1].sk_argument = v2;
- cur_skey[2].sk_argument = v3;
- cur_skey[3].sk_argument = v4;
+ /* Initialize local parameter array */
+ arguments[0] = v1;
+ arguments[1] = v2;
+ arguments[2] = v3;
+ arguments[3] = v4;
/*
* calculate the hash value
*/
- return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, cur_skey);
+ return CatalogCacheComputeHashValue(cache, cache->cc_nkeys, arguments);
}
@@ -1382,7 +1385,7 @@ SearchCatCacheList(CatCache *cache,
Datum v3,
Datum v4)
{
- ScanKeyData cur_skey[CATCACHE_MAXKEYS];
+ Datum arguments[CATCACHE_MAXKEYS];
uint32 lHashValue;
dlist_iter iter;
CatCList *cl;
@@ -1407,21 +1410,18 @@ SearchCatCacheList(CatCache *cache,
cache->cc_lsearches++;
#endif
- /*
- * initialize the search key information
- */
- memcpy(cur_skey, cache->cc_skey, sizeof(cur_skey));
- cur_skey[0].sk_argument = v1;
- cur_skey[1].sk_argument = v2;
- cur_skey[2].sk_argument = v3;
- cur_skey[3].sk_argument = v4;
+ /* Initialize local parameter array */
+ arguments[0] = v1;
+ arguments[1] = v2;
+ arguments[2] = v3;
+ arguments[3] = v4;
/*
* compute a hash value of the given keys for faster search. We don't
* presently divide the CatCList items into buckets, but this still lets
* us skip non-matching items quickly most of the time.
*/
- lHashValue = CatalogCacheComputeHashValue(cache, nkeys, cur_skey);
+ lHashValue = CatalogCacheComputeHashValue(cache, nkeys, arguments);
/*
* scan the items until we find a match or exhaust our list
@@ -1446,11 +1446,12 @@ SearchCatCacheList(CatCache *cache,
*/
if (cl->nkeys != nkeys)
continue;
- HeapKeyTest(&cl->tuple,
- cache->cc_tupdesc,
- nkeys,
- cur_skey,
- res);
+ HeapKeyTestArg(&cl->tuple,
+ cache->cc_tupdesc,
+ nkeys,
+ cache->cc_skey,
+ arguments,
+ res);
if (!res)
continue;
@@ -1494,9 +1495,20 @@ SearchCatCacheList(CatCache *cache,
PG_TRY();
{
+ ScanKeyData cur_skey[CATCACHE_MAXKEYS];
Relation relation;
SysScanDesc scandesc;
+ /*
+ * Ok, need to make a lookup in the relation, copy the scankey and fill out
+ * any per-call fields.
+ */
+ memcpy(cur_skey, cache->cc_skey, sizeof(ScanKeyData) * cache->cc_nkeys);
+ cur_skey[0].sk_argument = v1;
+ cur_skey[1].sk_argument = v2;
+ cur_skey[2].sk_argument = v3;
+ cur_skey[3].sk_argument = v4;
+
relation = heap_open(cache->cc_reloid, AccessShareLock);
scandesc = systable_beginscan(relation,
diff --git a/src/include/access/valid.h b/src/include/access/valid.h
index 8d9630a..4adcdfa 100644
--- a/src/include/access/valid.h
+++ b/src/include/access/valid.h
@@ -66,4 +66,60 @@ do \
} \
} while (0)
+/*
+ * HeapKeyTestArg
+ *
+ * Like HeapKeyTest test a heap tuple to see if it satisfies a scan key,
+ * but get the actual data from the separate 'arguments' parameter. That
+ * can be advantageous if it allows not to modify the scankey.
+ */
+#define HeapKeyTestArg(tuple, \
+ tupdesc, \
+ nkeys, \
+ keys, \
+ arguments, \
+ result) \
+do \
+{ \
+ /* Use underscores to protect the variables passed in as parameters */ \
+ int __cur_nkeys = (nkeys); \
+ ScanKey __cur_keys = (keys); \
+ Datum *__cur_args = (arguments); \
+ \
+ (result) = true; /* may change */ \
+ for (; __cur_nkeys--; __cur_keys++, __cur_args++) \
+ { \
+ Datum __atp; \
+ bool __isnull; \
+ Datum __test; \
+ \
+ if (__cur_keys->sk_flags & SK_ISNULL) \
+ { \
+ (result) = false; \
+ break; \
+ } \
+ \
+ __atp = heap_getattr((tuple), \
+ __cur_keys->sk_attno, \
+ (tupdesc), \
+ &__isnull); \
+ \
+ if (__isnull) \
+ { \
+ (result) = false; \
+ break; \
+ } \
+ \
+ __test = FunctionCall2Coll(&__cur_keys->sk_func, \
+ __cur_keys->sk_collation, \
+ __atp, *__cur_args); \
+ \
+ if (!DatumGetBool(__test)) \
+ { \
+ (result) = false; \
+ break; \
+ } \
+ } \
+} while (0)
+
#endif /* VALID_H */
--
1.8.3.251.g1462b67
On Thu, Sep 5, 2013 at 09:13:23PM +0200, Andres Freund wrote:
Hi,
Heikki's catcache rehashing stuff reminded me that I'd posted an
optimization to catcache (20121220153555.GH4303@awork2.anarazel.de) some
time back which I didn't have energy to pursue at that point.I've brushed the patch up a bit and verified it still gives be a
performance improvement. It's still about 2% in a readonly pgbench on my
elderly laptop.There's basically two tricks in the patch:
1) Don't always copy the cache's ScanKey to the stack. Instead pass
an array of arguments around. That get's rid of a good amount of
memcpy()ing in the common, cached case.
2) If we have to memcpy() because we need to pass a ScanKey to
systable_*, copy only cache->cc_nkey * sizeof(ScanKeyData) instead of
always copying the maximum size.I'd be nicer to get rid of the mostly copied HeapKeyTestArg, but I don't
immediately see how.
Where are we on this?
--
Bruce Momjian <bruce@momjian.us> http://momjian.us
EnterpriseDB http://enterprisedb.com
+ Everyone has their own god. +
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers