* Makefile, NEWS: Mention this. * localtime.c (THREAD_PREFER_SINGLE): Default to 0. (HAVE___ISTHREADED, HAVE_SYS_SINGLE_THREADED_H): Provide defaults. (dolock, dounlock): New functions, whose bodies are those of the old lock and unlock functions. (lock): Return -1 if known to be single threaded so no lock is needed. All callers changed. (unlock, rd2wrlock, tzset_unlocked): New arg THREADED. All callers changed. --- Makefile | 9 +++- NEWS | 7 +++ localtime.c | 125 ++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 106 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index c2c82c3a..8939869a 100644 --- a/Makefile +++ b/Makefile @@ -304,7 +304,14 @@ LDLIBS= # -DTHREAD_SAFE to make localtime.c thread-safe, as POSIX requires; # not needed by the main-program tz code, which is single-threaded. # Append other compiler flags as needed, e.g., -pthread on GNU/Linux. -# The following option can also be used: +# The following options can also be used: +# -DTHREAD_PREFER_SINGLE to prefer speed in single-threaded apps, +# at some cost in CPU time and energy in multi-threaded apps. +# The following options can also be used: +# -DHAVE___ISTHREADED=1 if there is an extern int __isthreaded +# variable, 0 otherwise (default is guessed) +# -DHAVE_SYS_SINGLE_THREADED_H=0 if <sys/single_threaded.h> works, +# 0 otherwise (default is guessed) # -DTHREAD_RWLOCK to use read-write locks intead of mutexes. # This can improve paralellism and thus save real time # if many threads call tzcode functions simultaneously. diff --git a/NEWS b/NEWS index 8ba97ab4..5bfb7bb4 100644 --- a/NEWS +++ b/NEWS @@ -68,6 +68,13 @@ Unreleased, experimental changes transition begun in release 96k, which removed spaces in tzdata because the spaces break time string parsers. + The new CFLAGS option -DTHREAD_PREFER_SINGLE causes tzcode + in single-threaded processes to avoid locks, as FreeBSD does. + This can save time in single-threaded apps. The threadedness + testing costs CPU time and energy in multi-threaded apps. + New options -DHAVE___ISTHREADED and -DHAVE_SYS_SINGLE_THREADED_H + can help configure how to test for single-threadedness. + The new CFLAGS option -DTHREAD_RWLOCK uses read-write locks, as macOS does, instead of mutexes. This saves real time when TZ is rarely changing and many threads call tzcode simultaneously. diff --git a/localtime.c b/localtime.c index 3a38ef04..01db7e70 100644 --- a/localtime.c +++ b/localtime.c @@ -50,31 +50,87 @@ struct stat { char st_ctime, st_dev, st_ino; } #if defined THREAD_SAFE && THREAD_SAFE # include <pthread.h> + +# ifndef THREAD_PREFER_SINGLE +# define THREAD_PREFER_SINGLE 0 +# endif +# if THREAD_PREFER_SINGLE +# ifndef HAVE___ISTHREADED +# if defined __FreeBSD__ || defined __OpenBSD__ +# define HAVE___ISTHREADED 1 +# else +# define HAVE___ISTHREADED 0 +# endif +# endif +# if HAVE___ISTHREADED +extern int __isthreaded; +# else +# if !defined HAVE_SYS_SINGLE_THREADED_H && defined __has_include +# if __has_include(<sys/single_threaded.h>) +# define HAVE_SYS_SINGLE_THREADED_H 1 +# else +# define HAVE_SYS_SINGLE_THREADED_H 0 +# endif +# endif +# ifndef HAVE_SYS_SINGLE_THREADED_H +# if defined __GLIBC__ && 2 < __GLIBC__ + (32 <= __GLIBC_MINOR__) +# define HAVE_SYS_SINGLE_THREADED_H 1 +# else +# define HAVE_SYS_SINGLE_THREADED_H 0 +# endif +# endif +# if HAVE_SYS_SINGLE_THREADED_H +# include <sys/single_threaded.h> +# endif +# endif +# endif + # if THREAD_RWLOCK static pthread_rwlock_t locallock = PTHREAD_RWLOCK_INITIALIZER; -static int lock(void) { return pthread_rwlock_rdlock(&locallock); } -static void unlock(void) { pthread_rwlock_unlock(&locallock); } +static int dolock(void) { return pthread_rwlock_rdlock(&locallock); } +static void dounlock(void) { pthread_rwlock_unlock(&locallock); } # else static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; -static int lock(void) { return pthread_mutex_lock(&locallock); } -static void unlock(void) { pthread_mutex_unlock(&locallock); } +static int dolock(void) { return pthread_mutex_lock(&locallock); } +static void dounlock(void) { pthread_mutex_unlock(&locallock); } # endif +/* Get a lock. Return 0 on success, a positive errno value on failure, + negative if known to be single-threaded so no lock is needed. */ +static int +lock(void) +{ +# if THREAD_PREFER_SINGLE && HAVE___ISTHREADED + if (!__isthreaded) + return -1; +# elif THREAD_PREFER_SINGLE && HAVE_SYS_SINGLE_THREADED_H + if (__libc_single_threaded) + return -1; +# endif + return dolock(); +} +static void +unlock(bool threaded) +{ + if (threaded) + dounlock(); +} #else -static int lock(void) { return 0; } -static void unlock(void) { } +static int lock(void) { return -1; } +static void unlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) { } #endif -/* Upgrade a read lock to a write lock. - Return 0 on success, an errno value otherwise. */ +/* If THREADED, upgrade a read lock to a write lock. + Return 0 on success, a positive errno value otherwise. */ static int -rd2wrlock(void) +rd2wrlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) { # if THREAD_RWLOCK - unlock(); - return pthread_rwlock_wrlock(&locallock); -# else - return 0; + if (threaded) { + dounlock(); + return pthread_rwlock_wrlock(&locallock); + } # endif + return 0; } /* Unless intptr_t is missing, pacify gcc -Wcast-qual on char const * exprs. @@ -1756,11 +1812,11 @@ zoneinit(struct state *sp, char const *name, char tzloadflags) } /* Like tzset(), but in a critical section. - If THREAD_RWLOCK the caller has a read lock, + If THREADED && THREAD_RWLOCK the caller has a read lock, and this function might ugrade it to a write lock. If tz_change_interval is positive the time is NOW; otherwise ignore NOW. */ static void -tzset_unlocked(monotime_t now) +tzset_unlocked(bool threaded, monotime_t now) { char const *name; struct state *sp; @@ -1800,7 +1856,7 @@ tzset_unlocked(monotime_t now) if (!THREAD_RWLOCK || writing) break; - if (rd2wrlock() != 0) + if (rd2wrlock(threaded) != 0) return; writing = true; } @@ -1853,12 +1909,12 @@ tzset(void) { monotime_t now = get_monotonic_time(); int err = lock(); - if (err != 0) { + if (0 < err) { errno = err; return; } - tzset_unlocked(now); - unlock(); + tzset_unlocked(!err, now); + unlock(!err); } #endif @@ -1866,10 +1922,11 @@ static void gmtcheck(void) { static bool gmt_is_set; - if (lock() != 0) + int err = lock(); + if (0 < err) return; if (! gmt_is_set) { - if (rd2wrlock() != 0) + if (rd2wrlock(!err) != 0) return; if (!THREAD_RWLOCK || !gmt_is_set) { #if ALL_STATE @@ -1880,7 +1937,7 @@ gmtcheck(void) gmt_is_set = true; } } - unlock(); + unlock(!err); } #if NETBSD_INSPIRED && !USE_TIMEX_T @@ -2047,14 +2104,14 @@ localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) { monotime_t now = get_monotonic_time(); int err = lock(); - if (err) { + if (0 < err) { errno = err; return NULL; } if (0 <= tz_change_interval || setname || !lcl_is_set) - tzset_unlocked(now); + tzset_unlocked(!err, now); tmp = localsub(lclptr, timep, setname, tmp); - unlock(); + unlock(!err); return tmp; } @@ -2715,13 +2772,13 @@ mktime(struct tm *tmp) monotime_t now = get_monotonic_time(); time_t t; int err = lock(); - if (err) { + if (0 < err) { errno = err; return -1; } - tzset_unlocked(now); + tzset_unlocked(!err, now); t = mktime_tzname(lclptr, tmp, true); - unlock(); + unlock(!err); return t; } @@ -2832,15 +2889,15 @@ time2posix(time_t t) { monotime_t now = get_monotonic_time(); int err = lock(); - if (err) { + if (0 < err) { errno = err; return -1; } if (0 <= tz_change_interval || !lcl_is_set) - tzset_unlocked(now); + tzset_unlocked(!err, now); if (lclptr) t = time2posix_z(lclptr, t); - unlock(); + unlock(!err); return t; } @@ -2878,15 +2935,15 @@ posix2time(time_t t) { monotime_t now = get_monotonic_time(); int err = lock(); - if (err) { + if (0 < err) { errno = err; return -1; } if (0 <= tz_change_interval || !lcl_is_set) - tzset_unlocked(now); + tzset_unlocked(!err, now); if (lclptr) t = posix2time_z(lclptr, t); - unlock(); + unlock(!err); return t; } -- 2.51.0