Hi, Heikki's catcache rehashing stuff reminded me that I'd posted an optimization to catcache (20121220153555.gh4...@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
>From df40dab17e7582ab597214d3c7fcefd953dcc9f7 Mon Sep 17 00:00:00 2001 From: Andres Freund <and...@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
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers