-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement the BRAVO biased reader/writer lock
The goal of the BRAVO biased rwlock is to avoid readers taking the reader lock and thus contending for the atomic variable. Instead, readers raise a flag in an array to signal that they "took the lock" to any future writer. A writer takes the underlying atomic lock and waits for all readers to complete. While a writer has the lock, readers wait for the atomic rwlock. The hash table in PaRSEC is a prime example for a use-case: writers are extremely rare since the resizing happens rarely and the max size is capped. However, every thread locking a bucket also takes a reader-lock. We can thus avoid the contention on the global lock for most of the application run. The original proposal used a global hash table for all locks (https://arxiv.org/abs/1810.01553) but we use one array per lock. We know the number of threads in PaRSEC and can use fixed offsets, with padding to prevent cache line sharing (64B per thread). If an unknown thread takes the lock it goes straight to the atomic rwlock (unfortuntely, this includes the MPI progress thread at the moment). Signed-off-by: Joseph Schuchart <schuchart@icl.utk.edu>
- Loading branch information
Showing
5 changed files
with
208 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* | ||
* Copyright (c) 2009-2022 The University of Tennessee and The University | ||
* of Tennessee Research Foundation. All rights | ||
* reserved. | ||
*/ | ||
|
||
#include "parsec/class/parsec_biased_rwlock.h" | ||
|
||
#include <assert.h> | ||
|
||
#include "parsec/runtime.h" | ||
#include "parsec/constants.h" | ||
#include "parsec/execution_stream.h" | ||
#include "parsec/sys/atomic.h" | ||
#include "parsec/class/parsec_rwlock.h" | ||
|
||
/** | ||
* An implementation of the BRAVO biased reader/writer lock wrapper. | ||
* The goal of the BRAVO lock wrapper is to avoid contending the atomic | ||
* rwlock with reader locks, instead having threads mark their read status | ||
* is an array. A writer will first take the rwlock, signal that a writer | ||
* is active, and then wait for all readers to complete. New readers will | ||
* see that a writer is active and wait for the reader lock to become available. | ||
* | ||
* This is clearly biased towards readers so this implementation is meant for | ||
* cases where the majority of accesses is reading and only occasional writes occur. | ||
* | ||
* The paper presenting this technique is available at: | ||
* https://arxiv.org/abs/1810.01553 | ||
* | ||
* While the original implementation uses a global hash table, we use a smaller table | ||
* per lock. In PaRSEC, we know the number of threads we control up front. | ||
* We simply pad for a cache line. If an unknown thread tries to take the lock against | ||
* all odds, it falls back to taking the reader lock. | ||
*/ | ||
|
||
struct parsec_biased_rwlock_t { | ||
parsec_atomic_rwlock_t rw_lock; /**< underlying reader-writer lock */ | ||
int32_t reader_bias; /**< whether locking is biased towards readers, will change if a writer occurs */ | ||
uint32_t num_reader; /**< size of the reader_active field */ | ||
uint8_t reader_active[]; /**< array with flags signalling reading threads */ | ||
}; | ||
|
||
#define DEFAULT_CACHE_SIZE 64 | ||
|
||
int parsec_biased_rwlock_init(parsec_biased_rwlock_t **lock) { | ||
parsec_biased_rwlock_t *res; | ||
parsec_execution_stream_t *es = parsec_my_execution_stream(); | ||
if (NULL == es) { | ||
/* should be called from a parsec thread */ | ||
res = (parsec_biased_rwlock_t *)malloc(sizeof(parsec_biased_rwlock_t)); | ||
res->num_reader = 0; | ||
res->reader_bias = 0; // disable reader biasing | ||
} else { | ||
uint32_t num_threads = es->virtual_process->nb_cores; | ||
/* one cache line per reader */ | ||
uint32_t num_reader = num_threads*DEFAULT_CACHE_SIZE; | ||
res = (parsec_biased_rwlock_t *)malloc(sizeof(parsec_biased_rwlock_t) + num_reader*sizeof(uint8_t)); | ||
parsec_atomic_rwlock_init(&res->rw_lock); | ||
res->reader_bias = 1; | ||
res->num_reader = num_reader; | ||
memset(res->reader_active, 0, num_reader); | ||
} | ||
*lock = res; | ||
|
||
return PARSEC_SUCCESS; | ||
} | ||
|
||
void parsec_biased_rwlock_rdlock(parsec_biased_rwlock_t *lock) | ||
{ | ||
parsec_execution_stream_t *es = parsec_my_execution_stream(); | ||
if (PARSEC_UNLIKELY(NULL == es || lock->num_reader == 0)) { | ||
/* fall back to the underlying rwlock */ | ||
parsec_atomic_rwlock_rdlock(&lock->rw_lock); | ||
return; | ||
} | ||
|
||
if (PARSEC_UNLIKELY(!lock->reader_bias)) { | ||
/* a writer is active, wait for the rwlock to become available */ | ||
parsec_atomic_rwlock_rdlock(&lock->rw_lock); | ||
return; | ||
} | ||
|
||
/* fast-path: no writer, simply mark as active reader and make sure there is no race */ | ||
size_t reader_entry = es->th_id*DEFAULT_CACHE_SIZE; | ||
assert(reader_entry >= 0 && reader_entry < lock->num_reader); | ||
assert(lock->reader_active[reader_entry] == 0); | ||
|
||
lock->reader_active[reader_entry] = 1; | ||
/* make sure the writer check is not moved to before setting the flag */ | ||
parsec_atomic_rmb(); | ||
/* double check that no writer came in between */ | ||
if (PARSEC_UNLIKELY(!lock->reader_bias)) { | ||
/* a writer has become active, fallback to the rwlock */ | ||
lock->reader_active[reader_entry] = 0; | ||
parsec_atomic_rwlock_rdlock(&lock->rw_lock); | ||
} | ||
} | ||
|
||
void parsec_biased_rwlock_rdunlock(parsec_biased_rwlock_t *lock) | ||
{ | ||
parsec_execution_stream_t *es = parsec_my_execution_stream(); | ||
|
||
if (PARSEC_UNLIKELY(NULL == es || lock->num_reader == 0)) { | ||
/* fall back to the underlying rwlock */ | ||
parsec_atomic_rwlock_rdunlock(&lock->rw_lock); | ||
return; | ||
} | ||
|
||
size_t reader_entry = es->th_id*DEFAULT_CACHE_SIZE; | ||
assert(reader_entry >= 0 && reader_entry < lock->num_reader); | ||
|
||
if (PARSEC_UNLIKELY(lock->reader_active[reader_entry] == 0)) { | ||
/* we had to take a lock, give it back */ | ||
parsec_atomic_rwlock_rdunlock(&lock->rw_lock); | ||
} else { | ||
lock->reader_active[reader_entry] = 0; | ||
} | ||
} | ||
|
||
void parsec_biased_rwlock_wrlock(parsec_biased_rwlock_t *lock) | ||
{ | ||
/* acquire the writer lock first */ | ||
parsec_atomic_rwlock_wrlock(&lock->rw_lock); | ||
|
||
lock->reader_bias = 0; | ||
|
||
/* make sure the reads below are not moved before the write */ | ||
parsec_atomic_wmb(); | ||
|
||
/* wait for all current reader to complete */ | ||
for (uint32_t i = 0; i < lock->num_reader; ++i) { | ||
while (lock->reader_active[i] != 0) { | ||
static struct timespec ts = { .tv_sec = 0, .tv_nsec = 100 }; | ||
nanosleep(&ts, NULL); | ||
} | ||
} | ||
} | ||
|
||
void parsec_biased_rwlock_wrunlock(parsec_biased_rwlock_t *lock) | ||
{ | ||
assert(lock->reader_bias == 0); | ||
if (lock->num_reader > 0) { | ||
/* re-enable reader bias, if we support it */ | ||
lock->reader_bias = 1; | ||
} | ||
parsec_atomic_rwlock_wrunlock(&lock->rw_lock); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/* | ||
* Copyright (c) 2009-2022 The University of Tennessee and The University | ||
* of Tennessee Research Foundation. All rights | ||
* reserved. | ||
*/ | ||
#ifndef _parsec_biased_rwlock_h | ||
#define _parsec_biased_rwlock_h | ||
|
||
#include "parsec/parsec_config.h" | ||
|
||
/** | ||
* An implementation of the BRAVO biased reader/writer lock wrapper. | ||
* The goal of the BRAVO lock wrapper is to avoid contending the atomic | ||
* rwlock with reader locks, instead having threads mark their read status | ||
* is an array. A writer will first take the rwlock, signal that a writer | ||
* is active, and then wait for all readers to complete. New readers will | ||
* see that a writer is active and wait for the reader lock to become available. | ||
* | ||
* This is clearly biased towards readers so this implementation is meant for | ||
* cases where the majority of accesses is reading and only occasional writes occur. | ||
* | ||
* The paper presenting this technique is available at: | ||
* https://arxiv.org/abs/1810.01553 | ||
* | ||
* While the original implementation uses a global hash table, we use a smaller table | ||
* per lock. In PaRSEC, we know the number of threads we control up front. | ||
* We simply pad for a cache line. If an unknown thread tries to take the lock against | ||
* all odds, it falls back to taking the reader lock. | ||
*/ | ||
|
||
/* fwd-decl */ | ||
typedef struct parsec_biased_rwlock_t parsec_biased_rwlock_t; | ||
|
||
int parsec_biased_rwlock_init(parsec_biased_rwlock_t **lock); | ||
|
||
void parsec_biased_rwlock_rdlock(parsec_biased_rwlock_t *lock); | ||
|
||
void parsec_biased_rwlock_rdunlock(parsec_biased_rwlock_t *lock); | ||
|
||
void parsec_biased_rwlock_wrlock(parsec_biased_rwlock_t *lock); | ||
|
||
void parsec_biased_rwlock_wrunlock(parsec_biased_rwlock_t *lock); | ||
|
||
#endif // _parsec_biased_rwlock_h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters