pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Aleksander Alekseev <a.alekseev@postgrespro.ru>
To: Robert Haas <robertmhaas@gmail.com>
Cc: Teodor Sigaev <teodor@sigaev.ru>
Cc: Andres Freund <andres@anarazel.de>
Cc: pgsql-hackers@postgresql.org <pgsql-hackers@postgresql.org>
Subject: Re: Patch: fix lock contention for HASHHDR.mutex
Date: Wed, 30 Dec 2015 16:01:37 +0300
Message-ID: <20151230160137.034371e0@fujitsu> (raw)
In-Reply-To: <20151229184851.1bb7d1bd@fujitsu>
References: <20151211170001.78ded9d7@fujitsu>
	<20151215122513.GC17724@alap3.anarazel.de>
	<CA+TgmoavsNuBNQf6ROn020F6g9gXJjtMrSkSFZ=RL1t2eJHQ5Q@mail.gmail.com>
	<20151217151418.GB2224@awork2.anarazel.de>
	<20151217190342.07e4533a@fujitsu>
	<CA+Tgmob1-7=Bh21LyLXk7Yst1nzcA1P=rv0UKq1zH+A9PVehrg@mail.gmail.com>
	<56740E41.4010708@sigaev.ru>
	<CA+Tgmobp1Dsk5M0UDjm91kCtt+LLJ+tTzs0nucw4Vw2ZfQx29w@mail.gmail.com>
	<20151222183953.771cb58b@fujitsu>
	<CA+TgmoZC6RsDt09LsYvSgmZFjy-Jwc6rD4GiySY3d4QTh8c3ow@mail.gmail.com>
	<20151228120112.6a847dfd@fujitsu>
	<20151229184851.1bb7d1bd@fujitsu>
List-Unsubscribe: <mailto:majordomo@postgresql.org?body=unsub%20pgsql-hackers>

Here is a clean version of the patch. Step 1 is an optimization. Step 2
refactors this:

 HTAB *
 ShmemInitHash(const char *name, /* table string name for shmem index */
-             long init_size,   /* initial table size */
+             long init_size,   /* initial table size XXX ignored,
 refactor! */


As I understand there is no performance degradation:

Before
======

Core i7, pgbench -j 8 -c 8 -T 50 pgbench:

```
number of transactions actually processed: 14315
latency average: 27.945 ms
latency stddev: 44.920 ms
tps = 286.097043 (including connections establishing)
tps = 286.127642 (excluding connections establishing)
```

60-core server, pgbench -j 64 -c 64 -T 50 pgbench:

```
number of transactions actually processed: 176127
latency average: 18.162 ms
latency stddev: 23.861 ms
tps = 3521.069672 (including connections establishing)
tps = 3522.166706 (excluding connections establishing)
```


After
=====

Core i7, pgbench -j 8 -c 8 -T 50 pgbench:

```
number of transactions actually processed: 14212
latency average: 27.984 ms
latency stddev: 43.250 ms
tps = 284.038588 (including connections establishing)
tps = 285.112772 (excluding connections establishing)
```

60-core server, pgbench -j 64 -c 64 -T 50 pgbench:

```
number of transactions actually processed: 172135
latency average: 17.767 ms
latency stddev: 22.692 ms
tps = 3590.410302 (including connections establishing)
tps = 3594.607165 (excluding connections establishing)
```

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Attachments:

dynahash-optimization-v10-step1.patch (text/x-patch, 12.6K) download diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 78f15f0..d361dbb 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -265,7 +265,7 @@ InitShmemIndex(void) */ HTAB * ShmemInitHash(const char *name, /* table string name for shmem index */ - long init_size, /* initial table size */ + long init_size, /* initial table size XXX ignored, refactor! */ long max_size, /* max size of the table */ HASHCTL *infoP, /* info about key and bucket size */ int hash_flags) /* info about infoP */ @@ -299,7 +299,7 @@ ShmemInitHash(const char *name, /* table string name for shmem index */ /* Pass location of hashtable header to hash_create */ infoP->hctl = (HASHHDR *) location; - return hash_create(name, init_size, infoP, hash_flags); + return hash_create(name, max_size, infoP, hash_flags); } /* diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c index eacffc4..a50a2d3 100644 --- a/src/backend/utils/hash/dynahash.c +++ b/src/backend/utils/hash/dynahash.c @@ -15,7 +15,7 @@ * to hash_create. This prevents any attempt to split buckets on-the-fly. * Therefore, each hash bucket chain operates independently, and no fields * of the hash header change after init except nentries and freeList. - * A partitioned table uses a spinlock to guard changes of those two fields. + * A partitioned table uses spinlocks to guard changes of those fields. * This lets any subset of the hash buckets be treated as a separately * lockable partition. We expect callers to use the low-order bits of a * lookup key's hash value as a partition number --- this will work because @@ -87,6 +87,7 @@ #include "access/xact.h" #include "storage/shmem.h" #include "storage/spin.h" +#include "storage/lock.h" #include "utils/dynahash.h" #include "utils/memutils.h" @@ -128,12 +129,21 @@ typedef HASHBUCKET *HASHSEGMENT; */ struct HASHHDR { - /* In a partitioned table, take this lock to touch nentries or freeList */ - slock_t mutex; /* unused if not partitioned table */ + /* + * In a partitioned table take these locks to touch nentries or freeList. + * If hash table is not partitioned mutexes are not used. + */ + slock_t mutex[NUM_LOCK_PARTITIONS]; + + /* + * These fields change during entry addition/deletion. If hash table is + * not partitioned only nentries[0] and freeList[0] are used. + */ - /* These fields change during entry addition/deletion */ - long nentries; /* number of entries in hash table */ - HASHELEMENT *freeList; /* linked list of free elements */ + /* number of entries in hash table */ + long nentries[NUM_LOCK_PARTITIONS]; + /* linked list of free elements */ + HASHELEMENT *freeList[NUM_LOCK_PARTITIONS]; /* These fields can change, but not in a partitioned table */ /* Also, dsize can't change in a shared table, even if unpartitioned */ @@ -166,6 +176,8 @@ struct HASHHDR #define IS_PARTITIONED(hctl) ((hctl)->num_partitions != 0) +#define PARTITION_IDX(hctl, hashcode) (IS_PARTITIONED(hctl) ? LockHashPartition(hashcode) : 0) + /* * Top control structure for a hashtable --- in a shared table, each backend * has its own copy (OK since no fields change at runtime) @@ -219,10 +231,10 @@ static long hash_accesses, */ static void *DynaHashAlloc(Size size); static HASHSEGMENT seg_alloc(HTAB *hashp); -static bool element_alloc(HTAB *hashp, int nelem); +static bool element_alloc(HTAB *hashp, int nelem, int partition_idx); static bool dir_realloc(HTAB *hashp); static bool expand_table(HTAB *hashp); -static HASHBUCKET get_hash_entry(HTAB *hashp); +static HASHBUCKET get_hash_entry(HTAB *hashp, int partition_idx); static void hdefault(HTAB *hashp); static int choose_nelem_alloc(Size entrysize); static bool init_htab(HTAB *hashp, long nelem); @@ -282,6 +294,9 @@ hash_create(const char *tabname, long nelem, HASHCTL *info, int flags) { HTAB *hashp; HASHHDR *hctl; + int i, + partitions_number, + nelem_alloc; /* * For shared hash tables, we have a local hash header (HTAB struct) that @@ -482,10 +497,20 @@ hash_create(const char *tabname, long nelem, HASHCTL *info, int flags) if ((flags & HASH_SHARED_MEM) || nelem < hctl->nelem_alloc) { - if (!element_alloc(hashp, (int) nelem)) - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), - errmsg("out of memory"))); + if (IS_PARTITIONED(hashp->hctl)) + partitions_number = NUM_LOCK_PARTITIONS; + else + partitions_number = 1; + + nelem_alloc = ((int) nelem) / partitions_number; + if (nelem_alloc == 0) + nelem_alloc = 1; + + for (i = 0; i < partitions_number; i++) + if (!element_alloc(hashp, nelem_alloc, i)) + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of memory"))); } if (flags & HASH_FIXED_SIZE) @@ -503,9 +528,6 @@ hdefault(HTAB *hashp) MemSet(hctl, 0, sizeof(HASHHDR)); - hctl->nentries = 0; - hctl->freeList = NULL; - hctl->dsize = DEF_DIRSIZE; hctl->nsegs = 0; @@ -572,12 +594,14 @@ init_htab(HTAB *hashp, long nelem) HASHSEGMENT *segp; int nbuckets; int nsegs; + int i; /* * initialize mutex if it's a partitioned table */ if (IS_PARTITIONED(hctl)) - SpinLockInit(&hctl->mutex); + for (i = 0; i < NUM_LOCK_PARTITIONS; i++) + SpinLockInit(&(hctl->mutex[i])); /* * Divide number of elements by the fill factor to determine a desired @@ -648,7 +672,7 @@ init_htab(HTAB *hashp, long nelem) "HIGH MASK ", hctl->high_mask, "LOW MASK ", hctl->low_mask, "NSEGS ", hctl->nsegs, - "NENTRIES ", hctl->nentries); + "NENTRIES ", hash_get_num_entries(hctl)); #endif return true; } @@ -769,7 +793,7 @@ hash_stats(const char *where, HTAB *hashp) where, hashp->hctl->accesses, hashp->hctl->collisions); fprintf(stderr, "hash_stats: entries %ld keysize %ld maxp %u segmentcount %ld\n", - hashp->hctl->nentries, (long) hashp->hctl->keysize, + hash_get_num_entries(hashp), (long) hashp->hctl->keysize, hashp->hctl->max_bucket, hashp->hctl->nsegs); fprintf(stderr, "%s: total accesses %ld total collisions %ld\n", where, hash_accesses, hash_collisions); @@ -863,6 +887,7 @@ hash_search_with_hash_value(HTAB *hashp, HASHBUCKET currBucket; HASHBUCKET *prevBucketPtr; HashCompareFunc match; + int partition_idx = PARTITION_IDX(hctl, hashvalue); #if HASH_STATISTICS hash_accesses++; @@ -885,7 +910,7 @@ hash_search_with_hash_value(HTAB *hashp, * order of these tests is to try to check cheaper conditions first. */ if (!IS_PARTITIONED(hctl) && !hashp->frozen && - hctl->nentries / (long) (hctl->max_bucket + 1) >= hctl->ffactor && + hctl->nentries[0] / (long) (hctl->max_bucket + 1) >= hctl->ffactor && !has_seq_scans(hashp)) (void) expand_table(hashp); } @@ -943,20 +968,20 @@ hash_search_with_hash_value(HTAB *hashp, { /* if partitioned, must lock to touch nentries and freeList */ if (IS_PARTITIONED(hctl)) - SpinLockAcquire(&hctl->mutex); + SpinLockAcquire(&(hctl->mutex[partition_idx])); - Assert(hctl->nentries > 0); - hctl->nentries--; + Assert(hctl->nentries[partition_idx] > 0); + hctl->nentries[partition_idx]--; /* remove record from hash bucket's chain. */ *prevBucketPtr = currBucket->link; /* add the record to the freelist for this table. */ - currBucket->link = hctl->freeList; - hctl->freeList = currBucket; + currBucket->link = hctl->freeList[partition_idx]; + hctl->freeList[partition_idx] = currBucket; if (IS_PARTITIONED(hctl)) - SpinLockRelease(&hctl->mutex); + SpinLockRelease(&hctl->mutex[partition_idx]); /* * better hope the caller is synchronizing access to this @@ -982,7 +1007,7 @@ hash_search_with_hash_value(HTAB *hashp, elog(ERROR, "cannot insert into frozen hashtable \"%s\"", hashp->tabname); - currBucket = get_hash_entry(hashp); + currBucket = get_hash_entry(hashp, partition_idx); if (currBucket == NULL) { /* out of memory */ @@ -1175,41 +1200,71 @@ hash_update_hash_key(HTAB *hashp, * create a new entry if possible */ static HASHBUCKET -get_hash_entry(HTAB *hashp) +get_hash_entry(HTAB *hashp, int partition_idx) { - HASHHDR *hctl = hashp->hctl; + HASHHDR *hctl = hashp->hctl; HASHBUCKET newElement; + int i, + borrow_from_idx; for (;;) { /* if partitioned, must lock to touch nentries and freeList */ if (IS_PARTITIONED(hctl)) - SpinLockAcquire(&hctl->mutex); + SpinLockAcquire(&hctl->mutex[partition_idx]); /* try to get an entry from the freelist */ - newElement = hctl->freeList; + newElement = hctl->freeList[partition_idx]; + if (newElement != NULL) - break; + { + /* remove entry from freelist, bump nentries */ + hctl->freeList[partition_idx] = newElement->link; + hctl->nentries[partition_idx]++; + if (IS_PARTITIONED(hctl)) + SpinLockRelease(&hctl->mutex[partition_idx]); + + return newElement; + } - /* no free elements. allocate another chunk of buckets */ if (IS_PARTITIONED(hctl)) - SpinLockRelease(&hctl->mutex); + SpinLockRelease(&hctl->mutex[partition_idx]); - if (!element_alloc(hashp, hctl->nelem_alloc)) + /* no free elements. allocate another chunk of buckets */ + if (!element_alloc(hashp, hctl->nelem_alloc, partition_idx)) { - /* out of memory */ - return NULL; - } - } + if (!IS_PARTITIONED(hctl)) + return NULL; /* out of memory */ - /* remove entry from freelist, bump nentries */ - hctl->freeList = newElement->link; - hctl->nentries++; + /* try to borrow element from another partition */ + borrow_from_idx = partition_idx; + for (;;) + { + borrow_from_idx = (borrow_from_idx + 1) % NUM_LOCK_PARTITIONS; + if (borrow_from_idx == partition_idx) + break; - if (IS_PARTITIONED(hctl)) - SpinLockRelease(&hctl->mutex); + SpinLockAcquire(&(hctl->mutex[borrow_from_idx])); + newElement = hctl->freeList[borrow_from_idx]; + + if (newElement != NULL) + { + hctl->freeList[borrow_from_idx] = newElement->link; + SpinLockRelease(&(hctl->mutex[borrow_from_idx])); + + SpinLockAcquire(&hctl->mutex[partition_idx]); + hctl->nentries[partition_idx]++; + SpinLockRelease(&hctl->mutex[partition_idx]); + + break; + } - return newElement; + SpinLockRelease(&(hctl->mutex[borrow_from_idx])); + } + + return newElement; + } + } } /* @@ -1218,11 +1273,21 @@ get_hash_entry(HTAB *hashp) long hash_get_num_entries(HTAB *hashp) { + int i; + long sum = hashp->hctl->nentries[0]; + /* * We currently don't bother with the mutex; it's only sensible to call * this function if you've got lock on all partitions of the table. */ - return hashp->hctl->nentries; + + if (!IS_PARTITIONED(hashp->hctl)) + return sum; + + for (i = 1; i < NUM_LOCK_PARTITIONS; i++) + sum += hashp->hctl->nentries[i]; + + return sum; } /* @@ -1530,9 +1595,9 @@ seg_alloc(HTAB *hashp) * allocate some new elements and link them into the free list */ static bool -element_alloc(HTAB *hashp, int nelem) +element_alloc(HTAB *hashp, int nelem, int partition_idx) { - HASHHDR *hctl = hashp->hctl; + HASHHDR *hctl = hashp->hctl; Size elementSize; HASHELEMENT *firstElement; HASHELEMENT *tmpElement; @@ -1563,14 +1628,14 @@ element_alloc(HTAB *hashp, int nelem) /* if partitioned, must lock to touch freeList */ if (IS_PARTITIONED(hctl)) - SpinLockAcquire(&hctl->mutex); + SpinLockAcquire(&hctl->mutex[partition_idx]); /* freelist could be nonempty if two backends did this concurrently */ - firstElement->link = hctl->freeList; - hctl->freeList = prevElement; + firstElement->link = hctl->freeList[partition_idx]; + hctl->freeList[partition_idx] = prevElement; if (IS_PARTITIONED(hctl)) - SpinLockRelease(&hctl->mutex); + SpinLockRelease(&hctl->mutex[partition_idx]); return true; } diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index ff34529..6e866f1 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -128,13 +128,13 @@ extern char *MainLWLockNames[]; * having this file include lock.h or bufmgr.h would be backwards. */ -/* Number of partitions of the shared buffer mapping hashtable */ -#define NUM_BUFFER_PARTITIONS 128 - /* Number of partitions the shared lock tables are divided into */ -#define LOG2_NUM_LOCK_PARTITIONS 4 +#define LOG2_NUM_LOCK_PARTITIONS 7 #define NUM_LOCK_PARTITIONS (1 << LOG2_NUM_LOCK_PARTITIONS) +/* Number of partitions of the shared buffer mapping hashtable */ +#define NUM_BUFFER_PARTITIONS NUM_LOCK_PARTITIONS + /* Number of partitions the shared predicate lock tables are divided into */ #define LOG2_NUM_PREDICATELOCK_PARTITIONS 4 #define NUM_PREDICATELOCK_PARTITIONS (1 << LOG2_NUM_PREDICATELOCK_PARTITIONS)
dynahash-optimization-v10-step2.patch (text/x-patch, 5.1K) download diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index cea84ee..656afc3 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -495,7 +495,7 @@ pgss_shmem_startup(void) info.hash = pgss_hash_fn; info.match = pgss_match_fn; pgss_hash = ShmemInitHash("pg_stat_statements hash", - pgss_max, pgss_max, + pgss_max, &info, HASH_ELEM | HASH_FUNCTION | HASH_COMPARE); diff --git a/src/backend/storage/buffer/buf_table.c b/src/backend/storage/buffer/buf_table.c index 6ed47d5..3d5aec7 100644 --- a/src/backend/storage/buffer/buf_table.c +++ b/src/backend/storage/buffer/buf_table.c @@ -62,7 +62,7 @@ InitBufTable(int size) info.num_partitions = NUM_BUFFER_PARTITIONS; SharedBufHash = ShmemInitHash("Shared Buffer Lookup Table", - size, size, + size, &info, HASH_ELEM | HASH_BLOBS | HASH_PARTITION); } diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index d361dbb..7c254ec 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -237,7 +237,7 @@ InitShmemIndex(void) hash_flags = HASH_ELEM; ShmemIndex = ShmemInitHash("ShmemIndex", - SHMEM_INDEX_SIZE, SHMEM_INDEX_SIZE, + SHMEM_INDEX_SIZE, &info, hash_flags); } @@ -255,17 +255,12 @@ InitShmemIndex(void) * exceeded substantially (since it's used to compute directory size and * the hash table buckets will get overfull). * - * init_size is the number of hashtable entries to preallocate. For a table - * whose maximum size is certain, this should be equal to max_size; that - * ensures that no run-time out-of-shared-memory failures can occur. - * * Note: before Postgres 9.0, this function returned NULL for some failure * cases. Now, it always throws error instead, so callers need not check * for NULL. */ HTAB * ShmemInitHash(const char *name, /* table string name for shmem index */ - long init_size, /* initial table size XXX ignored, refactor! */ long max_size, /* max size of the table */ HASHCTL *infoP, /* info about key and bucket size */ int hash_flags) /* info about infoP */ diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index 76fc615..378b54a 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -373,8 +373,7 @@ void InitLocks(void) { HASHCTL info; - long init_table_size, - max_table_size; + long max_table_size; bool found; /* @@ -382,7 +381,6 @@ InitLocks(void) * calculations must agree with LockShmemSize! */ max_table_size = NLOCKENTS(); - init_table_size = max_table_size / 2; /* * Allocate hash table for LOCK structs. This stores per-locked-object @@ -394,14 +392,12 @@ InitLocks(void) info.num_partitions = NUM_LOCK_PARTITIONS; LockMethodLockHash = ShmemInitHash("LOCK hash", - init_table_size, max_table_size, &info, HASH_ELEM | HASH_BLOBS | HASH_PARTITION); /* Assume an average of 2 holders per lock */ max_table_size *= 2; - init_table_size *= 2; /* * Allocate hash table for PROCLOCK structs. This stores @@ -413,7 +409,6 @@ InitLocks(void) info.num_partitions = NUM_LOCK_PARTITIONS; LockMethodProcLockHash = ShmemInitHash("PROCLOCK hash", - init_table_size, max_table_size, &info, HASH_ELEM | HASH_FUNCTION | HASH_PARTITION); diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index 4b4ad81..7898d81 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -1116,7 +1116,6 @@ InitPredicateLocks(void) PredicateLockTargetHash = ShmemInitHash("PREDICATELOCKTARGET hash", max_table_size, - max_table_size, &info, HASH_ELEM | HASH_BLOBS | HASH_PARTITION | HASH_FIXED_SIZE); @@ -1144,7 +1143,6 @@ InitPredicateLocks(void) PredicateLockHash = ShmemInitHash("PREDICATELOCK hash", max_table_size, - max_table_size, &info, HASH_ELEM | HASH_FUNCTION | HASH_PARTITION | HASH_FIXED_SIZE); @@ -1225,7 +1223,6 @@ InitPredicateLocks(void) SerializableXidHash = ShmemInitHash("SERIALIZABLEXID hash", max_table_size, - max_table_size, &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE); diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index c94d620..a361302 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -37,7 +37,7 @@ extern void InitShmemAllocation(void); extern void *ShmemAlloc(Size size); extern bool ShmemAddrIsValid(const void *addr); extern void InitShmemIndex(void); -extern HTAB *ShmemInitHash(const char *name, long init_size, long max_size, +extern HTAB *ShmemInitHash(const char *name, long max_size, HASHCTL *infoP, int hash_flags); extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr); extern Size add_size(Size s1, Size s2);

view thread (74+ messages)  latest in thread

Message-ID: <20151230160137.034371e0@fujitsu>
Permalink:  ../20151230160137.034371e0@fujitsu/
Also on:    postgresql.org/message-id/20151230160137.034371e0@fujitsu

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: a.alekseev@postgrespro.ru, robertmhaas@gmail.com, teodor@sigaev.ru, andres@anarazel.de
  Subject: Re: Patch: fix lock contention for HASHHDR.mutex
  In-Reply-To: <20151230160137.034371e0@fujitsu>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox