pthread_switch: fast user-level context switch
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 25 Apr 2014 23:29:04 +0000 (16:29 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 25 Apr 2014 23:32:48 +0000 (16:32 -0700)
This is just an example of what you can do.  Be a little careful if you
use this in another 2LS, since it short-circuits the rest of
uthread_yield.  I think it is okay as is, but future uthread changes
could cause trouble.  If we really need this behavior, I could build it
in to uthread.c.

Also, this adds an interface to pthreads, such that you can create a
thread, but not start running it yet.  Careful with that, too.

tests/pthread_switch.c [new file with mode: 0644]
user/pthread/pthread.c
user/pthread/pthread.h

diff --git a/tests/pthread_switch.c b/tests/pthread_switch.c
new file mode 100644 (file)
index 0000000..ffca972
--- /dev/null
@@ -0,0 +1,102 @@
+/* Copyright (c) 2014 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Basic pthread switcher, bypassing the 2LS.  Use for benchmarking and
+ * 2LS-inspiration. */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include "misc-compat.h"
+
+pthread_t th1, th2;
+int nr_switch_loops = 100;
+bool ready = FALSE;
+bool should_exit = FALSE;
+
+static void __pth_switch_cb(struct uthread *uthread, void *target)
+{
+       /* by not returning, this bypasses vcore entry and event checks, though when
+        * we pop back out of the 2LS, we'll check notif pending.  think about this
+        * if you put this into your 2LS. */
+       current_uthread = NULL;
+       run_uthread((struct uthread*)target);
+       assert(0);
+}
+
+static void pth_switch_to(struct pthread_tcb *target)
+{
+       uthread_yield(TRUE, __pth_switch_cb, target);
+}
+
+void *switch_thread(void *arg)
+{      
+       pthread_t other_thr = *(pthread_t*)arg;
+
+       while (!ready)
+               cpu_relax();
+       for (int i = 0; i < nr_switch_loops; i++) {
+               cmb();
+               if (should_exit)
+                       return 0;
+               pth_switch_to(other_thr);
+       }
+       if (pthread_self() == th1) {
+               /* we need to break out of the switching cycle.  when th2 runs again,
+                * it'll know to stop.  but th1 needs to both exit and switch to th2.
+                * we do this by making th2 runnable by the pth schedop, then exiting */
+               should_exit = TRUE;
+               /* we also need to do this to th2 before it tries to exit, o/w we'll PF
+                * in __pthread_generic_yield. */
+               sched_ops->thread_runnable((struct uthread*)th2);
+       }
+       return 0;
+}
+
+int main(int argc, char** argv) 
+{
+       struct timeval start_tv = {0};
+       struct timeval end_tv = {0};
+       long usec_diff;
+       long nr_ctx_switches;
+       void *join_ret;
+
+       if (argc > 1)
+               nr_switch_loops = strtol(argv[1], 0, 10);
+       printf("Making 2 threads of %d switches each\n", nr_switch_loops);
+
+       pthread_can_vcore_request(FALSE);       /* 2LS won't manage vcores */
+       pthread_need_tls(FALSE);
+       pthread_lib_init();                                     /* gives us one vcore */
+
+       /* each is passed the other's pthread_t.  th1 starts the switching. */
+       if (pthread_create(&th1, NULL, &switch_thread, &th2))
+               perror("pth_create 1 failed");
+       /* thread 2 is created, but not put on the runnable list */
+       if (__pthread_create(&th2, NULL, &switch_thread, &th1))
+               perror("pth_create 2 failed");
+
+       if (gettimeofday(&start_tv, 0))
+               perror("Start time error...");
+
+       ready = TRUE;                   /* signal to any spinning uthreads to start */
+
+       pthread_join(th1, &join_ret);
+       pthread_join(th2, &join_ret);
+
+       if (gettimeofday(&end_tv, 0))
+               perror("End time error...");
+       nr_ctx_switches = 2 * nr_switch_loops;
+       usec_diff = (end_tv.tv_sec - start_tv.tv_sec) * 1000000 +
+                   (end_tv.tv_usec - start_tv.tv_usec);
+       printf("Done: %d loops\n", nr_switch_loops);
+       printf("Nr context switches: %ld\n", nr_ctx_switches);
+       printf("Time to run: %ld usec\n", usec_diff);
+       printf("Context switch latency: %d nsec\n",
+              (int)(1000LL*usec_diff / nr_ctx_switches));
+       printf("Context switches / sec: %d\n\n",
+              (int)(1000000LL*nr_ctx_switches / usec_diff));
+} 
index ed99a64..8bd56ec 100644 (file)
@@ -466,8 +466,8 @@ void pthread_lib_init(void)
        atomic_init(&threads_total, 1);                 /* one for thread0 */
 }
 
-int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
-                   void *(*start_routine)(void *), void *arg)
+int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+                     void *(*start_routine)(void *), void *arg)
 {
        struct uth_thread_attr uth_attr = {0};
        run_once(pthread_lib_init());
@@ -503,12 +503,19 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        if (need_tls)
                uth_attr.want_tls = TRUE;
        uthread_init((struct uthread*)pthread, &uth_attr);
-       pth_thread_runnable((struct uthread*)pthread);
        *thread = pthread;
        atomic_inc(&threads_total);
        return 0;
 }
 
+int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
+                   void *(*start_routine)(void *), void *arg)
+{
+       if (!__pthread_create(thread, attr, start_routine, arg))
+               pth_thread_runnable((struct uthread*)*thread);
+       return 0;
+}
+
 /* Helper that all pthread-controlled yield paths call.  Just does some
  * accounting.  This is another example of how the much-loathed (and loved)
  * active queue is keeping us honest.  Need to export for sem and friends. */
index 1bd2ae9..8b4d765 100644 (file)
@@ -138,6 +138,8 @@ void __pthread_generic_yield(struct pthread_tcb *pthread);
 /* The pthreads API */
 int pthread_attr_init(pthread_attr_t *);
 int pthread_attr_destroy(pthread_attr_t *);
+int __pthread_create(pthread_t *, const pthread_attr_t *,
+                     void *(*)(void *), void *);
 int pthread_create(pthread_t *, const pthread_attr_t *,
                    void *(*)(void *), void *);
 int pthread_detach(pthread_t __th);