Improve sys/catcache performance.

The following are the individual improvements:
1) Avoidance of FunctionCallInfo based function calls, replaced by
   more efficient functions with a native C argument interface.
2) Don't extract columns from a cache entry's tuple whenever matching
   entries - instead store them as a Datum array. This also allows to
   get rid of having to build dummy tuples for negative & list
   entries, and of a hack for dealing with cstring vs. text weirdness.
3) Reorder members of catcache.h struct, so imortant entries are more
   likely to be on one cacheline.
4) Allowing the compiler to specialize critical SearchCatCache for a
   specific number of attributes allows to unroll loops and avoid
   other nkeys dependant initialization.
5) Only initializing the ScanKey when necessary, i.e. catcache misses,
   greatly reduces cache unnecessary cpu cache misses.
6) Split of the cache-miss case from the hash lookup, reducing stack
   allocations etc in the common case.
7) CatCTup and their corresponding heaptuple are allocated in one
   piece.

This results in making cache lookups themselves roughly three times as
fast - full-system benchmarks obviously improve less than that.

I've also evaluated further techniques:
- replace open coded hash with simplehash - the list walk right now
  shows up in profiles. Unfortunately it's not easy to do so safely as
  an entry's memory location can change at various times, which
  doesn't work well with the refcounting and cache invalidation.
- Cacheline-aligning CatCTup entries - helps some with performance,
  but the win isn't big and the code for it is ugly, because the
  tuples have to be freed as well.
- add more proper functions, rather than macros for
  SearchSysCacheCopyN etc., but right now they don't show up in
  profiles.

The reason the macro wrapper for syscache.c/h have to be changed,
rather than just catcache, is that doing otherwise would require
exposing the SysCache array to the outside.  That might be a good idea
anyway, but it's for another day.

Author: Andres Freund
Reviewed-By: Robert Haas
Discussion: https://postgr.es/m/20170914061207.zxotvyopetm7lrrp@alap3.anarazel.de
This commit is contained in:
Andres Freund 2017-10-13 13:16:50 -07:00
parent a0247e7a11
commit 141fd1b66c
5 changed files with 611 additions and 283 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1102,13 +1102,56 @@ SearchSysCache(int cacheId,
Datum key3,
Datum key4)
{
if (cacheId < 0 || cacheId >= SysCacheSize ||
!PointerIsValid(SysCache[cacheId]))
elog(ERROR, "invalid cache ID: %d", cacheId);
Assert(cacheId >= 0 && cacheId < SysCacheSize &&
PointerIsValid(SysCache[cacheId]));
return SearchCatCache(SysCache[cacheId], key1, key2, key3, key4);
}
HeapTuple
SearchSysCache1(int cacheId,
Datum key1)
{
Assert(cacheId >= 0 && cacheId < SysCacheSize &&
PointerIsValid(SysCache[cacheId]));
Assert(SysCache[cacheId]->cc_nkeys == 1);
return SearchCatCache1(SysCache[cacheId], key1);
}
HeapTuple
SearchSysCache2(int cacheId,
Datum key1, Datum key2)
{
Assert(cacheId >= 0 && cacheId < SysCacheSize &&
PointerIsValid(SysCache[cacheId]));
Assert(SysCache[cacheId]->cc_nkeys == 2);
return SearchCatCache2(SysCache[cacheId], key1, key2);
}
HeapTuple
SearchSysCache3(int cacheId,
Datum key1, Datum key2, Datum key3)
{
Assert(cacheId >= 0 && cacheId < SysCacheSize &&
PointerIsValid(SysCache[cacheId]));
Assert(SysCache[cacheId]->cc_nkeys == 3);
return SearchCatCache3(SysCache[cacheId], key1, key2, key3);
}
HeapTuple
SearchSysCache4(int cacheId,
Datum key1, Datum key2, Datum key3, Datum key4)
{
Assert(cacheId >= 0 && cacheId < SysCacheSize &&
PointerIsValid(SysCache[cacheId]));
Assert(SysCache[cacheId]->cc_nkeys == 4);
return SearchCatCache4(SysCache[cacheId], key1, key2, key3, key4);
}
/*
* ReleaseSysCache
* Release previously grabbed reference count on a tuple

View File

@ -34,25 +34,33 @@
#define CATCACHE_MAXKEYS 4
/* function computing a datum's hash */
typedef uint32 (*CCHashFN) (Datum datum);
/* function computing equality of two datums */
typedef bool (*CCFastEqualFN) (Datum a, Datum b);
typedef struct catcache
{
int id; /* cache identifier --- see syscache.h */
slist_node cc_next; /* list link */
int cc_nbuckets; /* # of hash buckets in this cache */
TupleDesc cc_tupdesc; /* tuple descriptor (copied from reldesc) */
dlist_head *cc_bucket; /* hash buckets */
CCHashFN cc_hashfunc[CATCACHE_MAXKEYS]; /* hash function for each key */
CCFastEqualFN cc_fastequal[CATCACHE_MAXKEYS]; /* fast equal function for
* each key */
int cc_keyno[CATCACHE_MAXKEYS]; /* AttrNumber of each key */
dlist_head cc_lists; /* list of CatCList structs */
int cc_ntup; /* # of tuples currently in this cache */
int cc_nkeys; /* # of keys (1..CATCACHE_MAXKEYS) */
const char *cc_relname; /* name of relation the tuples come from */
Oid cc_reloid; /* OID of relation the tuples come from */
Oid cc_indexoid; /* OID of index matching cache keys */
bool cc_relisshared; /* is relation shared across databases? */
TupleDesc cc_tupdesc; /* tuple descriptor (copied from reldesc) */
int cc_ntup; /* # of tuples currently in this cache */
int cc_nbuckets; /* # of hash buckets in this cache */
int cc_nkeys; /* # of keys (1..CATCACHE_MAXKEYS) */
int cc_key[CATCACHE_MAXKEYS]; /* AttrNumber of each key */
PGFunction cc_hashfunc[CATCACHE_MAXKEYS]; /* hash function for each key */
slist_node cc_next; /* list link */
ScanKeyData cc_skey[CATCACHE_MAXKEYS]; /* precomputed key info for heap
* scans */
bool cc_isname[CATCACHE_MAXKEYS]; /* flag "name" key columns */
dlist_head cc_lists; /* list of CatCList structs */
dlist_head *cc_bucket; /* hash buckets */
/*
* Keep these at the end, so that compiling catcache.c with CATCACHE_STATS
@ -79,7 +87,14 @@ typedef struct catctup
{
int ct_magic; /* for identifying CatCTup entries */
#define CT_MAGIC 0x57261502
CatCache *my_cache; /* link to owning catcache */
uint32 hash_value; /* hash value for this tuple's keys */
/*
* Lookup keys for the entry. By-reference datums point into the tuple for
* positive cache entries, and are separately allocated for negative ones.
*/
Datum keys[CATCACHE_MAXKEYS];
/*
* Each tuple in a cache is a member of a dlist that stores the elements
@ -88,15 +103,6 @@ typedef struct catctup
*/
dlist_node cache_elem; /* list member of per-bucket list */
/*
* The tuple may also be a member of at most one CatCList. (If a single
* catcache is list-searched with varying numbers of keys, we may have to
* make multiple entries for the same tuple because of this restriction.
* Currently, that's not expected to be common, so we accept the potential
* inefficiency.)
*/
struct catclist *c_list; /* containing CatCList, or NULL if none */
/*
* A tuple marked "dead" must not be returned by subsequent searches.
* However, it won't be physically deleted from the cache until its
@ -112,46 +118,63 @@ typedef struct catctup
int refcount; /* number of active references */
bool dead; /* dead but not yet removed? */
bool negative; /* negative cache entry? */
uint32 hash_value; /* hash value for this tuple's keys */
HeapTupleData tuple; /* tuple management header */
/*
* The tuple may also be a member of at most one CatCList. (If a single
* catcache is list-searched with varying numbers of keys, we may have to
* make multiple entries for the same tuple because of this restriction.
* Currently, that's not expected to be common, so we accept the potential
* inefficiency.)
*/
struct catclist *c_list; /* containing CatCList, or NULL if none */
CatCache *my_cache; /* link to owning catcache */
/* properly aligned tuple data follows, unless a negative entry */
} CatCTup;
/*
* A CatCList describes the result of a partial search, ie, a search using
* only the first K key columns of an N-key cache. We store the keys used
* into the keys attribute to represent the stored key set. The CatCList
* object contains links to cache entries for all the table rows satisfying
* the partial key. (Note: none of these will be negative cache entries.)
*
* A CatCList is only a member of a per-cache list; we do not currently
* divide them into hash buckets.
*
* A list marked "dead" must not be returned by subsequent searches.
* However, it won't be physically deleted from the cache until its
* refcount goes to zero. (A list should be marked dead if any of its
* member entries are dead.)
*
* If "ordered" is true then the member tuples appear in the order of the
* cache's underlying index. This will be true in normal operation, but
* might not be true during bootstrap or recovery operations. (namespace.c
* is able to save some cycles when it is true.)
*/
typedef struct catclist
{
int cl_magic; /* for identifying CatCList entries */
#define CL_MAGIC 0x52765103
CatCache *my_cache; /* link to owning catcache */
uint32 hash_value; /* hash value for lookup keys */
dlist_node cache_elem; /* list member of per-catcache list */
/*
* A CatCList describes the result of a partial search, ie, a search using
* only the first K key columns of an N-key cache. We form the keys used
* into a tuple (with other attributes NULL) to represent the stored key
* set. The CatCList object contains links to cache entries for all the
* table rows satisfying the partial key. (Note: none of these will be
* negative cache entries.)
*
* A CatCList is only a member of a per-cache list; we do not currently
* divide them into hash buckets.
*
* A list marked "dead" must not be returned by subsequent searches.
* However, it won't be physically deleted from the cache until its
* refcount goes to zero. (A list should be marked dead if any of its
* member entries are dead.)
*
* If "ordered" is true then the member tuples appear in the order of the
* cache's underlying index. This will be true in normal operation, but
* might not be true during bootstrap or recovery operations. (namespace.c
* is able to save some cycles when it is true.)
* Lookup keys for the entry, with the first nkeys elements being valid.
* All by-reference are separately allocated.
*/
dlist_node cache_elem; /* list member of per-catcache list */
Datum keys[CATCACHE_MAXKEYS];
int refcount; /* number of active references */
bool dead; /* dead but not yet removed? */
bool ordered; /* members listed in index order? */
short nkeys; /* number of lookup keys specified */
uint32 hash_value; /* hash value for lookup keys */
HeapTupleData tuple; /* header for tuple holding keys */
int n_members; /* number of member tuples */
CatCache *my_cache; /* link to owning catcache */
CatCTup *members[FLEXIBLE_ARRAY_MEMBER]; /* members */
} CatCList;
@ -174,8 +197,15 @@ extern CatCache *InitCatCache(int id, Oid reloid, Oid indexoid,
extern void InitCatCachePhase2(CatCache *cache, bool touch_index);
extern HeapTuple SearchCatCache(CatCache *cache,
Datum v1, Datum v2,
Datum v3, Datum v4);
Datum v1, Datum v2, Datum v3, Datum v4);
extern HeapTuple SearchCatCache1(CatCache *cache,
Datum v1);
extern HeapTuple SearchCatCache2(CatCache *cache,
Datum v1, Datum v2);
extern HeapTuple SearchCatCache3(CatCache *cache,
Datum v1, Datum v2, Datum v3);
extern HeapTuple SearchCatCache4(CatCache *cache,
Datum v1, Datum v2, Datum v3, Datum v4);
extern void ReleaseCatCache(HeapTuple tuple);
extern uint32 GetCatCacheHashValue(CatCache *cache,

View File

@ -117,6 +117,20 @@ extern void InitCatalogCachePhase2(void);
extern HeapTuple SearchSysCache(int cacheId,
Datum key1, Datum key2, Datum key3, Datum key4);
/*
* The use of argument specific numbers is encouraged. They're faster, and
* insulates the caller from changes in the maximum number of keys.
*/
extern HeapTuple SearchSysCache1(int cacheId,
Datum key1);
extern HeapTuple SearchSysCache2(int cacheId,
Datum key1, Datum key2);
extern HeapTuple SearchSysCache3(int cacheId,
Datum key1, Datum key2, Datum key3);
extern HeapTuple SearchSysCache4(int cacheId,
Datum key1, Datum key2, Datum key3, Datum key4);
extern void ReleaseSysCache(HeapTuple tuple);
/* convenience routines */
@ -156,15 +170,6 @@ extern bool RelationSupportsSysCache(Oid relid);
* functions is encouraged, as it insulates the caller from changes in the
* maximum number of keys.
*/
#define SearchSysCache1(cacheId, key1) \
SearchSysCache(cacheId, key1, 0, 0, 0)
#define SearchSysCache2(cacheId, key1, key2) \
SearchSysCache(cacheId, key1, key2, 0, 0)
#define SearchSysCache3(cacheId, key1, key2, key3) \
SearchSysCache(cacheId, key1, key2, key3, 0)
#define SearchSysCache4(cacheId, key1, key2, key3, key4) \
SearchSysCache(cacheId, key1, key2, key3, key4)
#define SearchSysCacheCopy1(cacheId, key1) \
SearchSysCacheCopy(cacheId, key1, 0, 0, 0)
#define SearchSysCacheCopy2(cacheId, key1, key2) \

View File

@ -302,6 +302,8 @@ CatCache
CatCacheHeader
CatalogId
CatalogIndexState
CCHashFN
CCFastEqualFN
ChangeVarNodes_context
CheckPoint
CheckPointStmt