Skip to content

Commit

Permalink
Speed up unnamed POSIX semaphores
Browse files Browse the repository at this point in the history
When sem_wait() used its futexes it would always use process shared mode
which can be problematic on platforms like Windows, where that causes it
to use the slow futex polyfill. Now when sem_init() is called in private
mode that'll be passed along so we can use a faster WaitOnAddress() call
  • Loading branch information
jart committed Sep 13, 2024
1 parent b5fcb59 commit 462ba69
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 30 deletions.
25 changes: 16 additions & 9 deletions libc/thread/sem_destroy.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/assert.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/strace.h"
#include "libc/limits.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/semaphore.h"
Expand All @@ -40,14 +41,20 @@
* @raise EBUSY if `sem` has waiters
*/
int sem_destroy(sem_t *sem) {
int waiters;
int rc, waiters;
npassert(sem->sem_magic != SEM_MAGIC_NAMED);
if (sem->sem_magic != SEM_MAGIC_UNNAMED)
return einval();
waiters = atomic_load_explicit(&sem->sem_waiters, memory_order_relaxed);
unassert(waiters >= 0);
if (waiters)
return ebusy();
atomic_store_explicit(&sem->sem_value, INT_MIN, memory_order_relaxed);
return 0;
if (sem->sem_magic != SEM_MAGIC_UNNAMED) {
rc = einval();
} else {
waiters = atomic_load_explicit(&sem->sem_waiters, memory_order_relaxed);
unassert(waiters >= 0);
if (waiters) {
rc = ebusy();
} else {
atomic_store_explicit(&sem->sem_value, INT_MIN, memory_order_relaxed);
rc = 0;
}
}
STRACE("sem_destroy(%p) → %d% m", sem, rc);
return rc;
}
22 changes: 14 additions & 8 deletions libc/thread/sem_init.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/strace.h"
#include "libc/limits.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/semaphore.h"
Expand All @@ -37,12 +38,17 @@
* @raise EINVAL if `value` exceeds `SEM_VALUE_MAX`
*/
int sem_init(sem_t *sem, int pshared, unsigned value) {
if (value > SEM_VALUE_MAX)
return einval();
sem->sem_magic = SEM_MAGIC_UNNAMED;
atomic_store_explicit(&sem->sem_value, value, memory_order_relaxed);
sem->sem_pshared = !!pshared;
sem->sem_pid = getpid();
sem->sem_waiters = 0;
return 0;
int rc;
if (value > SEM_VALUE_MAX) {
rc = einval();
} else {
sem->sem_magic = SEM_MAGIC_UNNAMED;
atomic_store_explicit(&sem->sem_value, value, memory_order_relaxed);
sem->sem_pshared = !!pshared;
sem->sem_pid = getpid();
sem->sem_waiters = 0;
rc = 0;
}
STRACE("sem_init(%p, %hhhd, %u) → %d% m", sem, pshared, value, rc);
return rc;
}
2 changes: 1 addition & 1 deletion libc/thread/sem_post.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ int sem_post(sem_t *sem) {
old = atomic_fetch_add_explicit(&sem->sem_value, 1, memory_order_acq_rel);
unassert(old > INT_MIN);
if (old >= 0) {
wakeups = nsync_futex_wake_(&sem->sem_value, 1, true);
wakeups = nsync_futex_wake_(&sem->sem_value, 1, sem->sem_pshared);
npassert(wakeups >= 0);
rc = 0;
} else {
Expand Down
22 changes: 10 additions & 12 deletions libc/thread/sem_timedwait.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ static void sem_timedwait_cleanup(void *arg) {
* @cancelationpoint
*/
int sem_timedwait(sem_t *sem, const struct timespec *abstime) {
int i, v, rc, e = errno;
int v, rc, e = errno;

#if 0
if (IsXnuSilicon() && sem->sem_magic == SEM_MAGIC_KERNEL) {
Expand Down Expand Up @@ -103,16 +103,13 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) {
}
#endif

for (i = 0; i < 7; ++i) {
rc = sem_trywait(sem);
if (!rc) {
return rc;
} else if (errno == EAGAIN) {
errno = e;
sem_delay(i);
} else {
return rc;
}
rc = sem_trywait(sem);
if (!rc) {
return rc;
} else if (errno == EAGAIN) {
errno = e;
} else {
return rc;
}

BEGIN_CANCELATION_POINT;
Expand All @@ -122,7 +119,8 @@ int sem_timedwait(sem_t *sem, const struct timespec *abstime) {

do {
if (!(v = atomic_load_explicit(&sem->sem_value, memory_order_relaxed))) {
rc = nsync_futex_wait_(&sem->sem_value, v, true, CLOCK_REALTIME, abstime);
rc = nsync_futex_wait_(&sem->sem_value, v, sem->sem_pshared,
CLOCK_REALTIME, abstime);
if (rc == -EINTR || rc == -ECANCELED) {
errno = -rc;
rc = -1;
Expand Down
35 changes: 35 additions & 0 deletions test/posix/unnamed_semaphore_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <pthread.h>
#include <semaphore.h>

#define THREADS 10
#define ITERATIONS 100000

int g_count;
sem_t g_sem;

void *worker(void *arg) {
for (int i = 0; i < ITERATIONS; ++i) {
if (sem_wait(&g_sem))
exit(6);
++g_count;
if (sem_post(&g_sem))
exit(7);
}
return 0;
}

int main(int argc, char *argv[]) {
pthread_t th[THREADS];
if (sem_init(&g_sem, 0, 1))
return 1;
for (int i = 0; i < THREADS; ++i)
if (pthread_create(&th[i], 0, worker, 0))
return 2;
for (int i = 0; i < THREADS; ++i)
if (pthread_join(th[i], 0))
return 3;
if (g_count != THREADS * ITERATIONS)
return 4;
if (sem_destroy(&g_sem))
return 5;
}

0 comments on commit 462ba69

Please sign in to comment.