Skip to content

Instantly share code, notes, and snippets.

@kosaki
Created May 5, 2011 10:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kosaki/000b3b46004dd6f1156f to your computer and use it in GitHub Desktop.
Save kosaki/000b3b46004dd6f1156f to your computer and use it in GitHub Desktop.
From c5c39df34ca9303b7cf47627abf7a5e7854bea4d Mon Sep 17 00:00:00 2001
From: KOSAKI Motohiro <kosaki.motohiro@gmail.com>
Date: Wed, 4 May 2011 20:55:38 +0900
Subject: [PATCH] gvl improve
Signed-off-by: KOSAKI Motohiro <kosaki.motohiro@gmail.com>
---
thread.c | 49 +++++++++++++++++-----------
thread_pthread.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++---
thread_pthread.h | 13 ++++++-
thread_win32.c | 5 ++-
vm_core.h | 5 ++-
5 files changed, 135 insertions(+), 29 deletions(-)
diff --git a/thread.c b/thread.c
index 1a3dabe..a72734c 100644
--- a/thread.c
+++ b/thread.c
@@ -1014,7 +1014,7 @@ rb_thread_sleep(int sec)
static void rb_threadptr_execute_interrupts_rec(rb_thread_t *, int);
static void
-rb_thread_schedule_rec(int sched_depth)
+rb_thread_schedule_rec(int sched_depth, unsigned long limits_ms)
{
thread_debug("rb_thread_schedule\n");
if (!rb_thread_alone()) {
@@ -1023,11 +1023,19 @@ rb_thread_schedule_rec(int sched_depth)
thread_debug("rb_thread_schedule/switch start\n");
RB_GC_SAVE_MACHINE_CONTEXT(th);
+
+#if HAVE_GVL_YIELD
+ {
+ if (th->running_time_ms >= limits_ms)
+ gvl_yield(th->vm, th);
+ }
+#else
gvl_release(th->vm);
{
native_thread_yield();
}
gvl_acquire(th->vm, th);
+#endif
rb_thread_set_current(th);
thread_debug("rb_thread_schedule/switch done\n");
@@ -1041,7 +1049,13 @@ rb_thread_schedule_rec(int sched_depth)
void
rb_thread_schedule(void)
{
- rb_thread_schedule_rec(0);
+ rb_thread_schedule_rec(0, 0);
+}
+
+void
+rb_thread_schedule_limit(unsigned long limits_ms)
+{
+ rb_thread_schedule_rec(0, limits_ms);
}
/* blocking region */
@@ -1278,7 +1292,8 @@ ruby_thread_has_gvl_p(void)
static VALUE
thread_s_pass(VALUE klass)
{
- rb_thread_schedule();
+ /* We need to avoid Thread.pass flood issue. It may hurt a performance. */
+ rb_thread_schedule_limit(100);
return Qnil;
}
@@ -1331,23 +1346,20 @@ rb_threadptr_execute_interrupts_rec(rb_thread_t *th, int sched_depth)
}
if (!sched_depth && timer_interrupt) {
- sched_depth++;
+ unsigned long limits_ms = 250;
+
+ if (th->priority > 0)
+ limits_ms <<= th->priority;
+ else
+ limits_ms >>= -th->priority;
+
+ if (status == THREAD_RUNNABLE)
+ th->running_time_ms += TIMER_THREAD_DURATION;
+
+ sched_depth++;
EXEC_EVENT_HOOK(th, RUBY_EVENT_SWITCH, th->cfp->self, 0, 0);
- if (th->slice > 0) {
- th->slice--;
- }
- else {
- reschedule:
- rb_thread_schedule_rec(sched_depth+1);
- if (th->slice < 0) {
- th->slice++;
- goto reschedule;
- }
- else {
- th->slice = th->priority;
- }
- }
+ rb_thread_schedule_rec(sched_depth+1, limits_ms);
}
}
}
@@ -2283,7 +2295,6 @@ rb_thread_priority_set(VALUE thread, VALUE prio)
priority = RUBY_THREAD_PRIORITY_MIN;
}
th->priority = priority;
- th->slice = priority;
#endif
return INT2NUM(th->priority);
}
diff --git a/thread_pthread.c b/thread_pthread.c
index 4161f66..5c94721 100644
--- a/thread_pthread.c
+++ b/thread_pthread.c
@@ -34,9 +34,10 @@ static void native_cond_destroy(pthread_cond_t *cond);
native_mutex_initialize(lock), \
native_mutex_lock(lock))
-#define GVL_SIMPLE_LOCK 0
+#define GVL_SIMPLE_LOCK 9
#define GVL_DEBUG 0
+#if !GVL_SIMPLE_LOCK
static void
gvl_show_waiting_threads(rb_vm_t *vm)
{
@@ -48,7 +49,6 @@ gvl_show_waiting_threads(rb_vm_t *vm)
}
}
-#if !GVL_SIMPLE_LOCK
static void
gvl_waiting_push(rb_vm_t *vm, rb_thread_t *th)
{
@@ -75,10 +75,36 @@ gvl_waiting_shift(rb_vm_t *vm, rb_thread_t *th)
#endif
static void
+gvl_acquire_locked(rb_vm_t *vm)
+{
+
+ if (vm->gvl.acquired) {
+ vm->gvl.waiting++;
+ while (vm->gvl.acquired) {
+ native_cond_wait(&vm->gvl.cond, &vm->gvl.lock);
+ }
+ vm->gvl.waiting--;
+
+ if (vm->gvl.need_yield) {
+ vm->gvl.need_yield = 0;
+ native_cond_signal(&vm->gvl.switch_cond);
+ }
+ }
+
+ vm->gvl.acquired = 1;
+}
+
+static void
gvl_acquire(rb_vm_t *vm, rb_thread_t *th)
{
#if GVL_SIMPLE_LOCK
+ #if GVL_SIMPLE_LOCK==1
native_mutex_lock(&vm->gvl.lock);
+ #elif GVL_SIMPLE_LOCK==9
+ native_mutex_lock(&vm->gvl.lock);
+ gvl_acquire_locked(vm);
+ native_mutex_unlock(&vm->gvl.lock);
+ #endif
#else
native_mutex_lock(&vm->gvl.lock);
if (vm->gvl.waiting > 0 || vm->gvl.acquired != 0) {
@@ -96,16 +122,30 @@ gvl_acquire(rb_vm_t *vm, rb_thread_t *th)
}
vm->gvl.acquired = 1;
native_mutex_unlock(&vm->gvl.lock);
-#endif
if (GVL_DEBUG) gvl_show_waiting_threads(vm);
if (GVL_DEBUG) fprintf(stderr, "gvl acquire (%p): acquire\n", (void *)th);
+#endif
+}
+
+static void
+gvl_release_locked(rb_vm_t *vm)
+{
+ vm->gvl.acquired = 0;
+ if (vm->gvl.waiting > 0)
+ native_cond_signal(&vm->gvl.cond);
}
static void
gvl_release(rb_vm_t *vm)
{
#if GVL_SIMPLE_LOCK
+ #if GVL_SIMPLE_LOCK==1
native_mutex_unlock(&vm->gvl.lock);
+ #elif GVL_SIMPLE_LOCK==9
+ native_mutex_lock(&vm->gvl.lock);
+ gvl_release_locked(vm);
+ native_mutex_unlock(&vm->gvl.lock);
+ #endif
#else
native_mutex_lock(&vm->gvl.lock);
if (vm->gvl.waiting > 0) {
@@ -122,13 +162,53 @@ gvl_release(rb_vm_t *vm)
#endif
}
+#if GVL_SIMPLE_LOCK == 9
+#define HAVE_GVL_YIELD 1
+static void
+gvl_yield(rb_vm_t *vm, rb_thread_t *th)
+{
+ native_mutex_lock(&vm->gvl.lock);
+
+ /* An another thread is processing GVL yield. */
+ if (vm->gvl.need_yield) {
+ native_mutex_unlock(&vm->gvl.lock);
+ return;
+ }
+
+ if (vm->gvl.waiting > 0)
+ vm->gvl.need_yield = 1;
+
+ gvl_release_locked(vm);
+ if (vm->gvl.need_yield) {
+ /* Wait until another thread task take GVL. */
+ native_cond_wait(&vm->gvl.switch_cond, &vm->gvl.lock);
+ } else {
+ native_mutex_unlock(&vm->gvl.lock);
+ sched_yield();
+ native_mutex_lock(&vm->gvl.lock);
+ }
+
+ gvl_acquire_locked(vm);
+ native_mutex_unlock(&vm->gvl.lock);
+}
+#endif
+
static void
gvl_init(rb_vm_t *vm)
{
if (GVL_DEBUG) fprintf(stderr, "gvl init\n");
#if GVL_SIMPLE_LOCK
+ #if GVL_SIMPLE_LOCK==1
+ native_mutex_initialize(&vm->gvl.lock);
+ #elif GVL_SIMPLE_LOCK==9
native_mutex_initialize(&vm->gvl.lock);
+ native_cond_initialize(&vm->gvl.cond);
+ native_cond_initialize(&vm->gvl.switch_cond);
+ vm->gvl.acquired = 0;
+ vm->gvl.waiting = 0;
+ vm->gvl.need_yield = 0;
+ #endif
#else
native_mutex_initialize(&vm->gvl.lock);
vm->gvl.waiting_threads = 0;
@@ -917,6 +997,8 @@ get_ts(struct timespec *ts, unsigned long nsec)
return ts;
}
+#define TIMER_THREAD_DURATION (50) /* milliseconds */
+
static void *
thread_timer(void *dummy)
{
@@ -924,9 +1006,9 @@ thread_timer(void *dummy)
native_mutex_lock(&timer_thread_lock);
native_cond_broadcast(&timer_thread_cond);
-#define WAIT_FOR_10MS() native_cond_timedwait(&timer_thread_cond, &timer_thread_lock, get_ts(&ts, PER_NANO/100))
while (system_working > 0) {
- int err = WAIT_FOR_10MS();
+ int err = native_cond_timedwait(&timer_thread_cond, &timer_thread_lock,
+ get_ts(&ts, TIMER_THREAD_DURATION*1000*1000));
if (err == ETIMEDOUT);
else if (err == 0 || err == EINTR) {
if (rb_signal_buff_size() == 0) break;
diff --git a/thread_pthread.h b/thread_pthread.h
index 3742cb2..d82c08a 100644
--- a/thread_pthread.h
+++ b/thread_pthread.h
@@ -29,11 +29,20 @@ typedef struct native_thread_data_struct {
#include <semaphore.h>
typedef struct rb_global_vm_lock_struct {
+ /* fast path */
+ unsigned long acquired;
pthread_mutex_t lock;
+
+ /* slow path */
+ unsigned long waiting;
+ pthread_cond_t cond;
+
+ /* yield */
+ pthread_cond_t switch_cond;
+ unsigned long need_yield;
+
struct rb_thread_struct * volatile waiting_threads;
struct rb_thread_struct *waiting_last_thread;
- int waiting;
- int volatile acquired;
} rb_global_vm_lock_t;
#endif /* RUBY_THREAD_PTHREAD_H */
diff --git a/thread_win32.c b/thread_win32.c
index e29c6af..fe96ff7 100644
--- a/thread_win32.c
+++ b/thread_win32.c
@@ -13,7 +13,8 @@
#include <process.h>
-#define WIN32_WAIT_TIMEOUT 10 /* 10 ms */
+#define TIMER_THREAD_DURATION (10) /* 10 ms */
+
#undef Sleep
#define native_thread_yield() Sleep(0)
@@ -626,7 +627,7 @@ static unsigned long _stdcall
timer_thread_func(void *dummy)
{
thread_debug("timer_thread\n");
- while (WaitForSingleObject(timer_thread_lock, WIN32_WAIT_TIMEOUT) ==
+ while (WaitForSingleObject(timer_thread_lock, TIMER_THREAD_DURATION) ==
WAIT_TIMEOUT) {
timer_thread_function(dummy);
}
diff --git a/vm_core.h b/vm_core.h
index c5417d9..9061dc8 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -418,7 +418,6 @@ typedef struct rb_thread_struct {
rb_thread_id_t thread_id;
enum rb_thread_status status;
int priority;
- int slice;
native_thread_data_t native_thread_data;
void *blocking_region_buffer;
@@ -484,6 +483,7 @@ typedef struct rb_thread_struct {
#ifdef USE_SIGALTSTACK
void *altstack;
#endif
+ unsigned long running_time_ms;
} rb_thread_t;
/* iseq.c */
@@ -679,6 +679,9 @@ extern rb_vm_t *ruby_current_vm;
#define GET_THREAD() ruby_current_thread
#define rb_thread_set_current_raw(th) (void)(ruby_current_thread = (th))
#define rb_thread_set_current(th) do { \
+ if ((th)->vm->running_thread != (th)) { \
+ (th)->vm->running_thread->running_time_ms = 0; \
+ } \
rb_thread_set_current_raw(th); \
(th)->vm->running_thread = (th); \
} while (0)
--
1.7.4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment