ruby-changes:52326
From: normal <ko1@a...>
Date: Sat, 25 Aug 2018 15:58:42 +0900 (JST)
Subject: [ruby-changes:52326] normal:r64533 (trunk): thread.c: use rb_hrtime_t scalar for high-resolution time operations
normal 2018-08-25 15:58:35 +0900 (Sat, 25 Aug 2018) New Revision: 64533 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=64533 Log: thread.c: use rb_hrtime_t scalar for high-resolution time operations Relying on "struct timespec" was too annoying API-wise and used more stack space. "double" was a bit wacky w.r.t rounding in the past, so now we'll switch to using a 64-bit type. Unsigned 64-bit integer is able to give us over nearly 585 years of range with nanoseconds. This range is good enough for the Linux kernel internal time representation, so it ought to be good enough for us. This reduces the stack usage of functions while GVL is held (and thus subject to marking) on x86-64 Linux (with ppoll): rb_wait_for_single_fd 120 => 104 do_select 120 => 88 [ruby-core:88582] [Misc #15014] Added files: trunk/hrtime.h Modified files: trunk/common.mk trunk/thread.c trunk/thread_pthread.c trunk/thread_sync.c trunk/thread_win32.c Index: thread_win32.c =================================================================== --- thread_win32.c (revision 64532) +++ thread_win32.c (revision 64533) @@ -276,11 +276,16 @@ rb_w32_Sleep(unsigned long msec) https://github.com/ruby/ruby/blob/trunk/thread_win32.c#L276 return ret; } +static DWORD +hrtime2msec(rb_hrtime_t hrt) +{ + return (DWORD)hrt / (DWORD)RB_HRTIME_PER_MSEC; +} + static void -native_sleep(rb_thread_t *th, struct timespec *ts) +native_sleep(rb_thread_t *th, rb_hrtime_t *rel) { - const volatile DWORD msec = (ts) ? - (DWORD)(ts->tv_sec * 1000 + ts->tv_nsec / 1000000) : INFINITE; + const volatile DWORD msec = rel ? hrtime2msec(*rel) : INFINITE; GVL_UNLOCK_BEGIN(th); { Index: thread_sync.c =================================================================== --- thread_sync.c (revision 64532) +++ thread_sync.c (revision 64533) @@ -248,8 +248,8 @@ do_mutex_lock(VALUE self, int interrupti https://github.com/ruby/ruby/blob/trunk/thread_sync.c#L248 while (mutex->th != th) { enum rb_thread_status prev_status = th->status; - struct timespec *timeout = 0; - struct timespec ts = { 0, 100000000 }; /* 100ms */ + rb_hrtime_t *timeout = 0; + rb_hrtime_t rel = rb_msec2hrtime(100); th->status = THREAD_STOPPED_FOREVER; th->locking_mutex = self; @@ -261,7 +261,7 @@ do_mutex_lock(VALUE self, int interrupti https://github.com/ruby/ruby/blob/trunk/thread_sync.c#L261 */ if ((vm_living_thread_num(th->vm) == th->vm->sleeper) && !patrol_thread) { - timeout = &ts; + timeout = &rel; patrol_thread = th; } @@ -458,8 +458,9 @@ rb_mutex_sleep_forever(VALUE time) https://github.com/ruby/ruby/blob/trunk/thread_sync.c#L458 static VALUE rb_mutex_wait_for(VALUE time) { - struct timespec *t = (struct timespec*)time; - sleep_timespec(GET_THREAD(), *t, 0); /* permit spurious check */ + rb_hrtime_t *rel = (rb_hrtime_t *)time; + /* permit spurious check */ + sleep_hrtime(GET_THREAD(), *rel, 0); return Qnil; } @@ -472,16 +473,17 @@ rb_mutex_sleep(VALUE self, VALUE timeout https://github.com/ruby/ruby/blob/trunk/thread_sync.c#L473 if (!NIL_P(timeout)) { t = rb_time_interval(timeout); } + rb_mutex_unlock(self); beg = time(0); if (NIL_P(timeout)) { rb_ensure(rb_mutex_sleep_forever, Qnil, mutex_lock_uninterruptible, self); } else { - struct timespec ts; - VALUE tsp = (VALUE)timespec_for(&ts, &t); + rb_hrtime_t rel = rb_timeval2hrtime(&t); - rb_ensure(rb_mutex_wait_for, tsp, mutex_lock_uninterruptible, self); + rb_ensure(rb_mutex_wait_for, (VALUE)&rel, + mutex_lock_uninterruptible, self); } RUBY_VM_CHECK_INTS_BLOCKING(GET_EC()); end = time(0) - beg; Index: thread_pthread.c =================================================================== --- thread_pthread.c (revision 64532) +++ thread_pthread.c (revision 64533) @@ -123,9 +123,9 @@ static void clear_thread_cache_altstack( https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L123 static void ubf_wakeup_all_threads(void); static int ubf_threads_empty(void); static int native_cond_timedwait(rb_nativethread_cond_t *, pthread_mutex_t *, - const struct timespec *); -static const struct timespec *sigwait_timeout(rb_thread_t *, int sigwait_fd, - const struct timespec *, + const rb_hrtime_t *abs); +static const rb_hrtime_t *sigwait_timeout(rb_thread_t *, int sigwait_fd, + const rb_hrtime_t *, int *drained_p); static void ubf_timer_disarm(void); static void threadptr_trap_interrupt(rb_thread_t *); @@ -159,8 +159,7 @@ static const void *const condattr_monoto https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L159 #define TIME_QUANTUM_USEC (TIME_QUANTUM_MSEC * 1000) #define TIME_QUANTUM_NSEC (TIME_QUANTUM_USEC * 1000) -static struct timespec native_cond_timeout(rb_nativethread_cond_t *, - struct timespec rel); +static rb_hrtime_t native_cond_timeout(rb_nativethread_cond_t *, rb_hrtime_t); /* * Designate the next gvl.timer thread, favor the last thread in @@ -186,19 +185,17 @@ designate_timer_thread(rb_vm_t *vm) https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L185 static void do_gvl_timer(rb_vm_t *vm, rb_thread_t *th) { - static struct timespec ts; + static rb_hrtime_t abs; native_thread_data_t *nd = &th->native_thread_data; /* take over wakeups from UBF_TIMER */ ubf_timer_disarm(); if (vm->gvl.timer_err == ETIMEDOUT) { - ts.tv_sec = 0; - ts.tv_nsec = TIME_QUANTUM_NSEC; - ts = native_cond_timeout(&nd->cond.gvlq, ts); + abs = native_cond_timeout(&nd->cond.gvlq, TIME_QUANTUM_NSEC); } vm->gvl.timer = th; - vm->gvl.timer_err = native_cond_timedwait(&nd->cond.gvlq, &vm->gvl.lock, &ts); + vm->gvl.timer_err = native_cond_timedwait(&nd->cond.gvlq, &vm->gvl.lock, &abs); vm->gvl.timer = 0; ubf_wakeup_all_threads(); @@ -499,9 +496,11 @@ rb_native_cond_wait(rb_nativethread_cond https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L496 } static int -native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *ts) +native_cond_timedwait(rb_nativethread_cond_t *cond, pthread_mutex_t *mutex, + const rb_hrtime_t *abs) { int r; + struct timespec ts; /* * An old Linux may return EINTR. Even though POSIX says @@ -510,7 +509,7 @@ native_cond_timedwait(rb_nativethread_co https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L509 * Let's hide it from arch generic code. */ do { - r = pthread_cond_timedwait(cond, mutex, ts); + r = pthread_cond_timedwait(cond, mutex, rb_hrtime2timespec(&ts, abs)); } while (r == EINTR); if (r != 0 && r != ETIMEDOUT) { @@ -520,20 +519,18 @@ native_cond_timedwait(rb_nativethread_co https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L519 return r; } -static struct timespec -native_cond_timeout(rb_nativethread_cond_t *cond, struct timespec timeout_rel) +static rb_hrtime_t +native_cond_timeout(rb_nativethread_cond_t *cond, const rb_hrtime_t rel) { - struct timespec abs; - if (condattr_monotonic) { - getclockofday(&abs); + return rb_hrtime_add(rb_hrtime_now(), rel); } else { - rb_timespec_now(&abs); - } - timespec_add(&abs, &timeout_rel); + struct timespec ts; - return abs; + rb_timespec_now(&ts); + return rb_hrtime_add(rb_timespec2hrtime(&ts), rel); + } } #define native_cleanup_push pthread_cleanup_push @@ -1056,13 +1053,13 @@ thread_cache_reset(void) https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1053 * worst case network latency across the globe) without wasting memory */ #ifndef THREAD_CACHE_TIME -# define THREAD_CACHE_TIME 3 +# define THREAD_CACHE_TIME ((rb_hrtime_t)3 * RB_HRTIME_PER_SEC) #endif static rb_thread_t * register_cached_thread_and_wait(void *altstack) { - struct timespec end = { THREAD_CACHE_TIME, 0 }; + rb_hrtime_t end = THREAD_CACHE_TIME; struct cached_thread_entry entry; rb_native_cond_initialize(&entry.cond); @@ -1218,28 +1215,20 @@ ubf_pthread_cond_signal(void *ptr) https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1215 } static void -native_cond_sleep(rb_thread_t *th, struct timespec *timeout_rel) +native_cond_sleep(rb_thread_t *th, rb_hrtime_t *rel) { - struct timespec timeout; rb_nativethread_lock_t *lock = &th->interrupt_lock; rb_nativethread_cond_t *cond = &th->native_thread_data.cond.intr; - if (timeout_rel) { - /* Solaris cond_timedwait() return EINVAL if an argument is greater than - * current_time + 100,000,000. So cut up to 100,000,000. This is - * considered as a kind of spurious wakeup. The caller to native_sleep - * should care about spurious wakeup. - * - * See also [Bug #1341] [ruby-core:29702] - * http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html - */ - if (timeout_rel->tv_sec > 100000000) { - timeout_rel->tv_sec = 100000000; - timeout_rel->tv_nsec = 0; - } - - timeout = native_cond_timeout(cond, *timeout_rel); - } + /* Solaris cond_timedwait() return EINVAL if an argument is greater than + * current_time + 100,000,000. So cut up to 100,000,000. This is + * considered as a kind of spurious wakeup. The caller to native_sleep + * should care about spurious wakeup. + * + * See also [Bug #1341] [ruby-core:29702] + * http://download.oracle.com/docs/cd/E19683-01/816-0216/6m6ngupgv/index.html + */ + const rb_hrtime_t max = (rb_hrtime_t)100000000 * RB_HRTIME_PER_SEC; GVL_UNLOCK_BEGIN(th); { @@ -1252,10 +1241,19 @@ native_cond_sleep(rb_thread_t *th, struc https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1241 thread_debug("native_sleep: interrupted before sleep\n"); } else { - if (!timeout_rel) + if (!rel) { rb_native_cond_wait(cond, lock); - else - native_cond_timedwait(cond, lock, &timeout); + } + else { + rb_hrtime_t end; + + if (*rel > max) { + *rel = max; + } + + end = native_cond_timeout(cond, *rel); + native_cond_timedwait(cond, lock, &end); + } } th->unblock.func = 0; @@ -1970,15 +1968,12 @@ rb_sigwait_sleep(rb_thread_t *th, int si https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1968 check_signals_nogvl(th, sigwait_fd); } else { - struct timespec end, diff; - const struct timespec *to; + rb_hrtime_t rel, end; int n = 0; if (ts) { - getclockofday(&end); - timespec_add(&end, ts); - diff = *ts; - ts = &diff; + rel = rb_timespec2hrtime(ts); + end = rb_hrtime_add(rb_hrtime_now(), rel); } /* * tricky: this needs to return on spurious wakeup (no auto-retry). @@ -1986,21 +1981,23 @@ rb_sigwait_sleep(rb_thread_t *th, int si https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L1981 * wakeups, so we care about the result of consume_communication_pipe */ for (;;) { - to = sigwait_timeout(th, sigwait_fd, ts, &n); + const rb_hrtime_t *sto = sigwait_timeout(th, sigwait_fd, &rel, &n); + struct timespec tmp; + if (n) return; - n = ppoll(&pfd, 1, to, 0); + n = ppoll(&pfd, 1, rb_hrtime2timespec(&tmp, sto), 0); if (check_signals_nogvl(th, sigwait_fd)) return; if (n || (th && RUBY_VM_INTERRUPTED(th->ec))) return; - if (ts && timespec_update_expire(&diff, &end)) + if (ts && hrtime_update_expire(&rel, end)) return; } } } static void -native_sleep(rb_thread_t *th, struct timespec *timeout_rel) +native_sleep(rb_thread_t *th, rb_hrtime_t *rel) { int sigwait_fd = rb_sigwait_fd_get(th); @@ -2012,7 +2009,8 @@ native_sleep(rb_thread_t *th, struct tim https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L2009 GVL_UNLOCK_BEGIN(th); if (!RUBY_VM_INTERRUPTED(th->ec)) { - rb_sigwait_sleep(th, sigwait_fd, timeout_rel); + struct timespec ts; + rb_sigwait_sleep(th, sigwait_fd, rb_hrtime2timespec(&ts, rel)); } else { check_signals_nogvl(th, sigwait_fd); @@ -2023,7 +2021,7 @@ native_sleep(rb_thread_t *th, struct tim https://github.com/ruby/ruby/blob/trunk/thread_pthread.c#L2021 rb_sigwait_fd_migrate(th->vm); } else { - native_cond_sleep(th, timeout_rel); + native_cond_sleep(th, rel); } } Index: thread.c =================================================================== --- thread.c (revision 64532) +++ thread.c (revision 64533) @@ -74,6 +74,7 @@ https://github.com/ruby/ruby/blob/trunk/thread.c#L74 #include "internal.h" #include "iseq.h" #include "vm_core.h" +#include "hrtime.h" #ifndef USE_NATIVE_THREAD_PRIORITY #define USE_NATIVE_THREAD_PRIORITY 0 @@ -97,18 +98,14 @@ enum SLEEP_FLAGS { https://github.com/ruby/ruby/blob/trunk/thread.c#L98 SLEEP_SPURIOUS_CHECK = 0x2 }; -static void sleep_timespec(rb_thread_t *, struct timespec, unsigned int fl); +static void sleep_hrtime(rb_thread_t *, rb_hrtime_t, unsigned int fl); static void sleep_forever(rb_thread_t *th, unsigned int fl); static void rb_thread_sleep_deadly_allow_spurious_wakeup(void); static int rb_threadptr_dead(rb_thread_t *th); static void rb_check_deadlock(rb_vm_t *vm); static int rb_threadptr_pending_interrupt_empty_p(const rb_thread_t *th); static const char *thread_status_name(rb_thread_t *th, int detail); -static void timespec_add(struct timespec *, const struct timespec *); -static void timespec_sub(struct timespec *, const struct timespec *); -static int timespec_cmp(const struct timespec *a, const struct timespec *b); -static int timespec_update_expire(struct timespec *, const struct timespec *); -static void getclockofday(struct timespec *); +static int hrtime_update_expire(rb_hrtime_t *, const rb_hrtime_t); NORETURN(static void async_bug_fd(const char *mesg, int errno_arg, int fd)); static int consume_communication_pipe(int fd); static int check_signals_nogvl(rb_thread_t *, int sigwait_fd); @@ -229,40 +226,17 @@ vm_living_thread_num(const rb_vm_t *vm) https://github.com/ruby/ruby/blob/trunk/thread.c#L226 # endif #endif -static struct timespec * -timespec_for(struct timespec *ts, const struct timeval *tv) -{ - if (tv) { - ts->tv_sec = tv->tv_sec; - ts->tv_nsec = tv->tv_usec * 1000; - return ts; - } - return 0; -} - -static struct timeval * -timeval_for(struct timeval *tv, const struct timespec *ts) -{ - if (tv && ts) { - tv->tv_sec = ts->tv_sec; - tv->tv_usec = (int32_t)(ts->tv_nsec / 1000); /* 10**6 < 2**(32-1) */ - return tv; - } - return 0; -} - static void -timeout_prepare(struct timespec **tsp, - struct timespec *ts, struct timespec *end, - const struct timeval *timeout) +timeout_prepare(rb_hrtime_t **to, rb_hrtime_t *rel, rb_hrtime_t *end, + const struct timeval *timeout) { if (timeout) { - getclockofday(end); - timespec_add(end, timespec_for(ts, timeout)); - *tsp = ts; + *rel = rb_timeval2hrtime(timeout); + *end = rb_hrtime_add(rb_hrtime_now(), *rel); + *to = rel; } else { - *tsp = 0; + *to = 0; } } @@ -596,13 +570,13 @@ rb_thread_terminate_all(void) https://github.com/ruby/ruby/blob/trunk/thread.c#L570 terminate_all(vm, th); while (vm_living_thread_num(vm) > 1) { - struct timespec ts = { 1, 0 }; + rb_hrtime_t rel = RB_HRTIME_PER_SEC; /* * Thread exiting routine in thread_start_func_2 notify * me when the last sub-thread exit. */ sleeping = 1; - native_sleep(th, &ts); + native_sleep(th, &rel); RUBY_VM_CHECK_INTS_BLOCKING(ec); sleeping = 0; } @@ -931,7 +905,7 @@ rb_thread_create(VALUE (*fn)(ANYARGS), v https://github.com/ruby/ruby/blob/trunk/thread.c#L905 struct join_arg { rb_thread_t *target, *waiting; - struct timespec *limit; + rb_hrtime_t *limit; }; static VALUE @@ -960,11 +934,10 @@ thread_join_sleep(VALUE arg) https://github.com/ruby/ruby/blob/trunk/thread.c#L934 { struct join_arg *p = (struct join_arg *)arg; rb_thread_t *target_th = p->target, *th = p->waiting; - struct timespec end; + rb_hrtime_t end; if (p->limit) { - getclockofday(&end); - timespec_add(&end, p->limit); + end = rb_hrtime_add(*p->limit, rb_hrtime_now()); } while (target_th->status != THREAD_KILLED) { @@ -976,7 +949,7 @@ thread_join_sleep(VALUE arg) https://github.com/ruby/ruby/blob/trunk/thread.c#L949 th->vm->sleeper--; } else { - if (timespec_update_expire(p->limit, &end)) { + if (hrtime_update_expire(p->limit, end)) { thread_debug("thread_join: timeout (thid: %"PRI_THREAD_ID")\n", thread_id_str(target_th)); return Qfalse; @@ -993,7 +966,7 @@ thread_join_sleep(VALUE arg) https://github.com/ruby/ruby/blob/trunk/thread.c#L966 } static VALUE -thread_join(rb_thread_t *target_th, struct timespec *ts) +thread_join(rb_thread_t *target_th, rb_hrtime_t *rel) { rb_thread_t *th = GET_THREAD(); struct join_arg arg; @@ -1007,7 +980,7 @@ thread_join(rb_thread_t *target_th, stru https://github.com/ruby/ruby/blob/trunk/thread.c#L980 arg.target = target_th; arg.waiting = th; - arg.limit = ts; + arg.limit = rel; thread_debug("thread_join (thid: %"PRI_THREAD_ID", status: %s)\n", thread_id_str(target_th), thread_status_name(target_th, TRUE)); @@ -1052,7 +1025,7 @@ thread_join(rb_thread_t *target_th, stru https://github.com/ruby/ruby/blob/trunk/thread.c#L1025 return target_th->self; } -static struct timespec *double2timespec(struct timespec *, double); +static rb_hrtime_t *double2hrtime(rb_hrtime_t *, double); /* * call-seq: @@ -1097,8 +1070,7 @@ static VALUE https://github.com/ruby/ruby/blob/trunk/thread.c#L1070 thread_join_m(int argc, VALUE *argv, VALUE self) { VALUE limit; - struct timespec timespec; - struct timespec *ts = 0; + rb_hrtime_t rel, *to = 0; rb_scan_args(argc, argv, "01", &limit); @@ -1109,17 +1081,14 @@ thread_join_m(int argc, VALUE *argv, VAL https://github.com/ruby/ruby/blob/trunk/thread.c#L1081 switch (TYPE(limit)) { case T_NIL: break; case T_FIXNUM: - timespec.tv_sec = NUM2TIMET(limit); - if (timespec.tv_sec < 0) - timespec.tv_sec = 0; - timespec.tv_nsec = 0; - ts = ×pec; + rel = rb_sec2hrtime(NUM2TIMET(limit)); + to = &rel; break; default: - ts = double2timespec(×pec, rb_num2dbl(limit)); + to = double2hrtime(&rel, rb_num2dbl(limit)); } - return thread_join(rb_thread_ptr(self), ts); + return thread_join(rb_thread_ptr(self), to); } /* @@ -1158,8 +1127,8 @@ thread_value(VALUE self) https://github.com/ruby/ruby/blob/trunk/thread.c#L1127 #define TIMESPEC_SEC_MAX TIMET_MAX #define TIMESPEC_SEC_MIN TIMET_MIN -static struct timespec * -double2timespec(struct timespec *ts, double d) +static rb_hrtime_t * +double2hrtime(rb_hrtime_t *hrt, double d) { /* assume timespec.tv_sec has same signedness as time_t */ const double TIMESPEC_SEC_MAX_PLUS_ONE = TIMET_MAX_PLUS_ONE; @@ -1168,18 +1137,31 @@ double2timespec(struct timespec *ts, dou https://github.com/ruby/ruby/blob/trunk/thread.c#L1137 return NULL; } else if (d <= 0) { - ts->tv_sec = 0; - ts->tv_nsec = 0; + *hrt = 0; } else { - ts->tv_sec = (time_t)d; - ts->tv_nsec = (long)((d - (time_t)d) * 1e9); - if (ts->tv_nsec < 0) { - ts->tv_nsec += (long)1e9; - ts->tv_sec -= 1; - } + *hrt = (rb_hrtime_t)(d * (double)RB_HRTIME_PER_SEC); } - return ts; + return hrt; +} + +static void +getclockofday(struct timespec *ts) +{ +#if defined(HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC) + if (clock_gettime(CLOCK_MONOTONIC, ts) == 0) + return; +#endif + rb_timespec_now(ts); +} + +rb_hrtime_t +rb_hrtime_now(void) +{ + struct timespec ts; + + getclockofday(&ts); + return rb_timespec2hrtime(&ts); } static void @@ -1208,102 +1190,39 @@ sleep_forever(rb_thread_t *th, unsigned https://github.com/ruby/ruby/blob/trunk/thread.c#L1190 th->status = prev_status; (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/