On Wed, Jun 19, 2013 at 01:17:03PM -0700, Ben Pfaff wrote: > POSIX defines a portable pthread_key_t API for per-thread data. GCC and > C11 have two different forms of per-thread data that are generally faster > than the POSIX API, where they are available. This commit adds a > macro-based wrapper, DEFINE_PER_THREAD_DATA, that takes advantage of the > GCC extension where it is available and falls back to the POSIX API > otherwise. (I'm not aware of any compilers that implement the C11 feature, > so this commit doesn't try to use it.)
Ed Maste pointed out off-list that clang on FreeBSD supports _Thread_local. Here's a revised version of the patch that supports both _Thread_local and __thread. I've also updated the "reviews" branch. --8<--------------------------cut here-------------------------->8-- From: Ben Pfaff <b...@nicira.com> Date: Thu, 20 Jun 2013 13:11:26 -0700 Subject: [PATCH] ovs-thread: Add per-thread data support. POSIX defines a portable pthread_key_t API for per-thread data. GCC and C11 have two different forms of per-thread data that are generally faster than the POSIX API, where they are available. This commit adds a macro-based wrapper, DEFINE_PER_THREAD_DATA, that takes advantage of these features where they are available and falls back to the POSIX API otherwise. The Clang compiler implements the C11 _Thread_local keyword. This commit also adds a convenience wrapper for the POSIX API, via the DEFINE_PER_THREAD_MALLOCED_DATA macro. Signed-off-by: Ben Pfaff <b...@nicira.com> --- configure.ac | 1 + lib/ovs-thread.h | 194 +++++++++++++++++++++++++++++++++++++++++++++++++++++ m4/openvswitch.m4 | 37 ++++++++++- 3 files changed, 231 insertions(+), 1 deletions(-) diff --git a/configure.ac b/configure.ac index a691963..6c610c1 100644 --- a/configure.ac +++ b/configure.ac @@ -80,6 +80,7 @@ OVS_CHECK_XENSERVER_VERSION OVS_CHECK_GROFF OVS_CHECK_GNU_MAKE OVS_CHECK_CACHE_TIME +OVS_CHECK__THREAD_LOCAL OVS_ENABLE_OPTION([-Wall]) OVS_ENABLE_OPTION([-Wno-sign-compare]) diff --git a/lib/ovs-thread.h b/lib/ovs-thread.h index cafeedf..d5bf048 100644 --- a/lib/ovs-thread.h +++ b/lib/ovs-thread.h @@ -85,5 +85,199 @@ void xpthread_cond_wait(pthread_cond_t *, pthread_mutex_t *mutex) void xpthread_key_create(pthread_key_t *, void (*destructor)(void *)); void xpthread_create(pthread_t *, pthread_attr_t *, void *(*)(void *), void *); + +/* Per-thread data. + * + * Multiple forms of per-thread data exist, each with its own pluses and + * minuses: + * + * - POSIX per-thread data via pthread_key_t is portable to any pthreads + * implementation, and allows a destructor function to be defined. It + * only (directly) supports per-thread pointers, which are always + * initialized to NULL. It requires once-only allocation of a + * pthread_key_t value. It is relatively slow. + * + * - The _Thread_local keyword newly defined in C11 works with any data + * type and initializer, and it is fast. _Thread_local does not require + * once-only initialization like pthread_key_t. C11 does not define what + * happens if one attempts to access a _Thread_local object from a thread + * other than the one to which that object belongs. There is no + * provision to call a user-specified destructor when a thread ends. + * + * - The __thread keyword is a GCC extension similar to _Thread_local but + * with a longer history. __thread is not portable to every GCC version + * or environment. __thread does not restrict the use of a thread-local + * object outside its own thread. + * + * Here's a handy summary: + * + * pthread_key_t _Thread_local __thread + * ------------- ------------- ------------- + * portability high low medium + * speed low high high + * supports destructors? yes no no + * needs key allocation? yes no no + * arbitrary initializer? no yes yes + * cross-thread access? yes no yes + */ + +/* DEFINE_PER_THREAD_DATA(TYPE, NAME, INITIALIZER). + * + * One should prefer to use POSIX per-thread data, via pthread_key_t, when its + * performance is acceptable, because of its portability (see the table above). + * This macro is an alternatives that takes advantage of _Thread_local (and + * __thread, via a config.h macro substitution), for its performance, when it + * is available, and falls back to POSIX per-thread data otherwise. + * + * Defines per-thread variable NAME with the given TYPE, initialized to + * INITIALIZER (which must be valid as an initializer for a variable with + * static lifetime). + * + * The public interface to the variable is: + * + * TYPE *NAME_get(void) + * TYPE *NAME_get__(void) + * + * Returns the address of this thread's instance of NAME. + * + * Use NAME_get() in a context where this might be the first use of the + * per-thread variable in the program. Use NAME_get__(), which avoids a + * conditional test and is thus slightly faster, in a context where one + * knows that NAME_get() has already been called previously. + * + * There are no "NAME_set()" or "NAME_set__()" functions. To set the value of + * the per-thread variable, dereference the pointer returned by TYPE_get() or + * TYPE_get__(), e.g. *TYPE_get() = 0. + */ +#if HAVE__THREAD_LOCAL +#define DEFINE_PER_THREAD_DATA(TYPE, NAME, ...) \ + typedef TYPE NAME##_type; \ + static _Thread_local NAME##_type NAME##_var = __VA_ARGS__; \ + \ + static NAME##_type * \ + NAME##_get__(void) \ + { \ + return &NAME##_var; \ + } \ + \ + static NAME##_type * \ + NAME##_get(void) \ + { \ + return NAME##_get__(); \ + } +#else +#define DEFINE_PER_THREAD_DATA(TYPE, NAME, ...) \ + typedef TYPE NAME##_type; \ + static pthread_key_t NAME##_key; \ + \ + static NAME##_type * \ + NAME##_get__(void) \ + { \ + return pthread_getspecific(NAME##_key); \ + } \ + \ + static void \ + NAME##_once_init(void) \ + { \ + if (pthread_key_create(&NAME##_key, free)) { \ + abort(); \ + } \ + } \ + \ + static NAME##_type * \ + NAME##_get(void) \ + { \ + static pthread_once_t once = PTHREAD_ONCE_INIT; \ + NAME##_type *value; \ + \ + pthread_once(&once, NAME##_once_init); \ + value = NAME##_get__(); \ + if (!value) { \ + static const NAME##_type initial_value = __VA_ARGS__; \ + \ + value = xmalloc(sizeof *value); \ + *value = initial_value; \ + pthread_setspecific(NAME##_key, value); \ + } \ + return value; \ + } +#endif + +/* DEFINE_PER_THREAD_MALLOCED_DATA(TYPE, NAME). + * + * This is a simple wrapper around POSIX per-thread data primitives. It + * defines per-thread variable NAME with the given TYPE, which must be a + * pointer type. In each thread, the per-thread variable is initialized to + * NULL. When a thread terminates, the variable is freed with free(). + * + * The public interface to the variable is: + * + * TYPE NAME_get(void) + * TYPE NAME_get__(void) + * + * Returns the value of per-thread variable NAME in this thread. + * + * Use NAME_get() in a context where this might be the first use of the + * per-thread variable in the program. Use NAME_get__(), which avoids a + * conditional test and is thus slightly faster, in a context where one + * knows that NAME_get() has already been called previously. + * + * TYPE NAME_set(TYPE new_value) + * TYPE NAME_set__(TYPE new_value) + * + * Sets the value of per-thread variable NAME to 'new_value' in this + * thread, and returns its previous value. + * + * Use NAME_set() in a context where this might be the first use of the + * per-thread variable in the program. Use NAME_set__(), which avoids a + * conditional test and is thus slightly faster, in a context where one + * knows that NAME_set() has already been called previously. + */ +#define DEFINE_PER_THREAD_MALLOCED_DATA(TYPE, NAME) \ + static pthread_key_t NAME##_key; \ + \ + static void \ + NAME##_once_init(void) \ + { \ + if (pthread_key_create(&NAME##_key, free)) { \ + abort(); \ + } \ + } \ + \ + static void \ + NAME##_init(void) \ + { \ + static pthread_once_t once = PTHREAD_ONCE_INIT; \ + pthread_once(&once, NAME##_once_init); \ + } \ + \ + static TYPE \ + NAME##_get__(void) \ + { \ + return pthread_getspecific(NAME##_key); \ + } \ + \ + static OVS_UNUSED TYPE \ + NAME##_get(void) \ + { \ + NAME##_init(); \ + return NAME##_get__(); \ + } \ + \ + static TYPE \ + NAME##_set__(TYPE value) \ + { \ + TYPE old_value = NAME##_get__(); \ + pthread_setspecific(NAME##_key, value); \ + return old_value; \ + } \ + \ + static OVS_UNUSED TYPE \ + NAME##_set(TYPE value) \ + { \ + NAME##_init(); \ + return NAME##_set__(value); \ + } + #endif /* ovs-thread.h */ diff --git a/m4/openvswitch.m4 b/m4/openvswitch.m4 index 12c02c0..3895346 100644 --- a/m4/openvswitch.m4 +++ b/m4/openvswitch.m4 @@ -1,6 +1,6 @@ # -*- autoconf -*- -# Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc. +# Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -390,3 +390,38 @@ AC_DEFUN([OVS_CHECK_GROFF], ovs_cv_groff=no fi]) AM_CONDITIONAL([HAVE_GROFF], [test "$ovs_cv_groff" = yes])]) + +dnl OVS_CHECK__THREAD_LOCAL +dnl +dnl Checks whether the compiler and linker support the C11 +dnl _Thread_local storage class or the GCC __thread extension, and if +dnl so define HAVE__THREAD_LOCAL. If __thread is supported, also #defines +dnl _Thread_local to __thread to hide the difference. +AC_DEFUN([OVS_CHECK__THREAD_LOCAL], + [AC_CACHE_CHECK( + [whether $CC supports _Thread_local], + [ovs_cv__Thread_local], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM([static _Thread_local var;], [return var;])], + [ovs_cv__Thread_local=yes], + [ovs_cv__Thread_local=no])]) + if test $ovs_cv__Thread_local = no; then + AC_CACHE_CHECK( + [whether $CC supports __thread], + [ovs_cv___thread], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM([static __thread var;], [return var;])], + [ovs_cv___thread=yes], + [ovs_cv___thread=no])]) + if test $ovs_cv___thread = yes; then + AC_DEFINE([_Thread_local], [__thread], + [Define _Thread_local to use GCC __thread extension.]) + fi + fi + if test $ovs_cv__Thread_local = yes || test $ovs_cv___thread = yes; then + AC_DEFINE( + [HAVE__THREAD_LOCAL], + [1], + [Define to 1 if the C compiler and linker support the C11 _Thread_local + storage class or the compatible GCC __thread extension.]) + fi]) -- 1.7.2.5 _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev