pthread: Make pthread barriers 2LS-independent
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 28 Apr 2017 16:36:44 +0000 (12:36 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 3 May 2017 16:16:41 +0000 (12:16 -0400)
This is one step towards making pthreads a generic API that can be used
when *any* 2LS is used.  Long term, we'll need that so library code can
make pthread calls (e.g. pthread_create(), pthread_mutex_lock(), etc.) and
access the real 2LS.  The alternative is porting every POSIX library that
wants to grab a thread.

Thanks to the previous commits, we're able to swap in uthreads for pthreads
and use sync object with bulk wakeups directly.  The first couple
implementations of this didn't have bulk wakeups or called
uthread_has_blocked() while holding the barrier's lock.  Both of those
noticeably hurt performance.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
user/pthread/pthread.c
user/pthread/pthread.h

index 00adb42..88281e9 100644 (file)
@@ -1026,34 +1026,6 @@ int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
        return 0;
 }
 
        return 0;
 }
 
-static void swap_slists(struct pthread_list *a, struct pthread_list *b)
-{
-       struct pthread_list temp;
-
-       temp = *a;
-       *a = *b;
-       *b = temp;
-}
-
-static void wake_slist(struct pthread_list *to_wake)
-{
-       unsigned int nr_woken = 0;      /* assuming less than 4 bil threads */
-       struct pthread_tcb *pthread_i, *pth_temp;
-       /* Amortize the lock grabbing over all restartees */
-       mcs_pdr_lock(&queue_lock);
-       /* Do the work of pth_thread_runnable().  We're in uth context here, but I
-        * think it's okay.  When we need to (when locking) we drop into VC ctx, as
-        * far as the kernel and other cores are concerned. */
-       SLIST_FOREACH_SAFE(pthread_i, to_wake, sl_next, pth_temp) {
-               pthread_i->state = PTH_RUNNABLE;
-               nr_woken++;
-               TAILQ_INSERT_TAIL(&ready_queue, pthread_i, tq_next);
-       }
-       threads_ready += nr_woken;
-       mcs_pdr_unlock(&queue_lock);
-       vcore_request_more(threads_ready);
-}
-
 int pthread_barrier_init(pthread_barrier_t *b,
                          const pthread_barrierattr_t *a, int count)
 {
 int pthread_barrier_init(pthread_barrier_t *b,
                          const pthread_barrierattr_t *a, int count)
 {
@@ -1061,7 +1033,7 @@ int pthread_barrier_init(pthread_barrier_t *b,
        b->sense = 0;
        atomic_set(&b->count, count);
        spin_pdr_init(&b->lock);
        b->sense = 0;
        atomic_set(&b->count, count);
        spin_pdr_init(&b->lock);
-       SLIST_INIT(&b->waiters);
+       __uth_sync_init(&b->waiters);
        b->nr_waiters = 0;
        return 0;
 }
        b->nr_waiters = 0;
        return 0;
 }
@@ -1086,30 +1058,26 @@ struct barrier_junk {
 /* TODO: consider making this a 2LS op */
 static inline bool safe_to_spin(unsigned int *state)
 {
 /* TODO: consider making this a 2LS op */
 static inline bool safe_to_spin(unsigned int *state)
 {
-       return !threads_ready;
+       return (*state)++ % PTHREAD_BARRIER_SPINS;
 }
 
 /* Callback/bottom half of barrier. */
 static void __pth_barrier_cb(struct uthread *uthread, void *junk)
 {
 }
 
 /* Callback/bottom half of barrier. */
 static void __pth_barrier_cb(struct uthread *uthread, void *junk)
 {
-       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
        pthread_barrier_t *b = ((struct barrier_junk*)junk)->b;
        int ls = ((struct barrier_junk*)junk)->ls;
        pthread_barrier_t *b = ((struct barrier_junk*)junk)->b;
        int ls = ((struct barrier_junk*)junk)->ls;
-       /* Removes from active list, we can reuse.  must also restart */
-       __pthread_generic_yield(pthread);
+
+       uthread_has_blocked(uthread, UTH_EXT_BLK_MUTEX);
        /* TODO: if we used a trylock, we could bail as soon as we see sense */
        spin_pdr_lock(&b->lock);
        /* If sense is ls (our free value), we lost the race and shouldn't sleep */
        if (b->sense == ls) {
        /* TODO: if we used a trylock, we could bail as soon as we see sense */
        spin_pdr_lock(&b->lock);
        /* If sense is ls (our free value), we lost the race and shouldn't sleep */
        if (b->sense == ls) {
-               /* TODO: i'd like to fast-path the wakeup, skipping pth_runnable */
-               pthread->state = PTH_BLK_YIELDING;      /* not sure which state for this */
                spin_pdr_unlock(&b->lock);
                spin_pdr_unlock(&b->lock);
-               pth_thread_runnable(uthread);
+               uthread_runnable(uthread);
                return;
        }
        /* otherwise, we sleep */
                return;
        }
        /* otherwise, we sleep */
-       pthread->state = PTH_BLK_MUTEX; /* TODO: consider ignoring this */
-       SLIST_INSERT_HEAD(&b->waiters, pthread, sl_next);
+       __uth_sync_enqueue(uthread, &b->waiters);
        b->nr_waiters++;
        spin_pdr_unlock(&b->lock);
 }
        b->nr_waiters++;
        spin_pdr_unlock(&b->lock);
 }
@@ -1132,15 +1100,13 @@ int pthread_barrier_wait(pthread_barrier_t *b)
 {
        unsigned int spin_state = 0;
        int ls = !b->sense;     /* when b->sense is the value we read, then we're free*/
 {
        unsigned int spin_state = 0;
        int ls = !b->sense;     /* when b->sense is the value we read, then we're free*/
-       struct pthread_list restartees = SLIST_HEAD_INITIALIZER(restartees);
-       struct pthread_tcb *pthread_i;
+       uth_sync_t restartees;
+       struct uthread *uth_i;
        struct barrier_junk local_junk;
        
        long old_count = atomic_fetch_and_add(&b->count, -1);
 
        if (old_count == 1) {
        struct barrier_junk local_junk;
        
        long old_count = atomic_fetch_and_add(&b->count, -1);
 
        if (old_count == 1) {
-               printd("Thread %d is last to hit the barrier, resetting...\n",
-                      pthread_self()->id);
                /* TODO: we might want to grab the lock right away, so a few short
                 * circuit faster? */
                atomic_set(&b->count, b->total_threads);
                /* TODO: we might want to grab the lock right away, so a few short
                 * circuit faster? */
                atomic_set(&b->count, b->total_threads);
@@ -1156,10 +1122,11 @@ int pthread_barrier_wait(pthread_barrier_t *b)
                        spin_pdr_unlock(&b->lock);
                        return PTHREAD_BARRIER_SERIAL_THREAD;
                }
                        spin_pdr_unlock(&b->lock);
                        return PTHREAD_BARRIER_SERIAL_THREAD;
                }
-               swap_slists(&restartees, &b->waiters);
+               __uth_sync_init(&restartees);
+               __uth_sync_swap(&restartees, &b->waiters);
                b->nr_waiters = 0;
                spin_pdr_unlock(&b->lock);
                b->nr_waiters = 0;
                spin_pdr_unlock(&b->lock);
-               wake_slist(&restartees);
+               __uth_sync_wake_all(&restartees);
                return PTHREAD_BARRIER_SERIAL_THREAD;
        } else {
                /* Spin if there are no other threads to run.  No sense sleeping */
                return PTHREAD_BARRIER_SERIAL_THREAD;
        } else {
                /* Spin if there are no other threads to run.  No sense sleeping */
@@ -1180,8 +1147,8 @@ int pthread_barrier_wait(pthread_barrier_t *b)
 
 int pthread_barrier_destroy(pthread_barrier_t *b)
 {
 
 int pthread_barrier_destroy(pthread_barrier_t *b)
 {
-       assert(SLIST_EMPTY(&b->waiters));
        assert(!b->nr_waiters);
        assert(!b->nr_waiters);
+       __uth_sync_destroy(&b->waiters);
        /* Free any locks (if we end up using an MCS) */
        return 0;
 }
        /* Free any locks (if we end up using an MCS) */
        return 0;
 }
index 675655d..dbc7d3a 100644 (file)
@@ -38,11 +38,7 @@ SLIST_HEAD(pthread_cleanup_stack, pthread_cleanup_routine);
 struct pthread_tcb;
 struct pthread_tcb {
        struct uthread uthread;
 struct pthread_tcb;
 struct pthread_tcb {
        struct uthread uthread;
-       union {
-               /* Only on one list at a time */
-               TAILQ_ENTRY(pthread_tcb) tq_next;
-               SLIST_ENTRY(pthread_tcb) sl_next;
-       };
+       TAILQ_ENTRY(pthread_tcb) tq_next;
        int state;
        uint32_t id;
        uint32_t stacksize;
        int state;
        uint32_t id;
        uint32_t stacksize;
@@ -54,7 +50,6 @@ struct pthread_tcb {
        struct pthread_cleanup_stack cr_stack;
 };
 typedef struct pthread_tcb* pthread_t;
        struct pthread_cleanup_stack cr_stack;
 };
 typedef struct pthread_tcb* pthread_t;
-SLIST_HEAD(pthread_list, pthread_tcb);
 TAILQ_HEAD(pthread_queue, pthread_tcb);
 
 /* Per-vcore data structures to manage syscalls.  The ev_q is where we tell the
 TAILQ_HEAD(pthread_queue, pthread_tcb);
 
 /* Per-vcore data structures to manage syscalls.  The ev_q is where we tell the
@@ -108,7 +103,7 @@ typedef struct
        volatile int                            sense;  /* state of barrier, flips btw runs */
        atomic_t                                        count;
        struct spin_pdr_lock            lock;
        volatile int                            sense;  /* state of barrier, flips btw runs */
        atomic_t                                        count;
        struct spin_pdr_lock            lock;
-       struct pthread_list                     waiters;
+       uth_sync_t                                      waiters;
        int                                                     nr_waiters;
 } pthread_barrier_t;
 
        int                                                     nr_waiters;
 } pthread_barrier_t;