From: Evan Phoenix Date: 2009-01-13T18:19:39+09:00 Subject: [ruby-core:21302] [PATCH] drastically improve rb_waitpid for short-lived children --Apple-Mail-6-128149838 Content-Type: text/plain; charset=US-ASCII; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit This evening I wrote this patch while helping a friend diagnose why, when Threads are off, `system("date")` takes 2.2ms, and when Threads are on, 60ms. The radical difference is a symptom of MRI's green threads and how you wait for a child in unix. The current implementation of system immediately calls rb_syswait(), which calls rb_waitpid() right after forking the child. rb_waitpid() calls waitpid(..., WNOHANG), which almost always returns nothing, since the child probably hasn't even actually started yet. My patch improves upon this by adding a 5ms 'quicksleep' zone. At intervals of 0.5ms for the first 5ms after rb_waitpid is called, waitpid() is called in a loop. This allows short-lived, fast exiting children to be caught and processed almost right away, without incurring the 60ms minimum time we currently have. The patch also adds a poll time backoff for use after the first 5ms. Rather than jumping right to backing off for 60ms, it backs off increments up to 60ms. This allows for children children that take just a little bit longer to be processed a bit quicker too. For long running children, the behavior is the same. This improves behavior a lot for users who run a lot of small, fast programs as children, in true unix style. - Evan Phoenix // evan@fallingsnow.net --Apple-Mail-6-128149838 Content-Disposition: attachment; filename=ruby-187-fast-waitpid.diff Content-Type: application/octet-stream; x-unix-mode=0644; name="ruby-187-fast-waitpid.diff" Content-Transfer-Encoding: 7bit diff -u -r ruby-1.8.7-p72/eval.c ruby-1.8.7-p72-mod/eval.c --- ruby-1.8.7-p72/eval.c 2008-08-03 20:24:26.000000000 -0700 +++ ruby-1.8.7-p72-mod/eval.c 2009-01-13 00:48:28.000000000 -0800 @@ -11777,6 +11777,17 @@ struct timeval rb_time_timeval(); void +rb_thread_polling_at(double time) +{ + if (curr_thread != curr_thread->next) { + curr_thread->status = THREAD_STOPPED; + curr_thread->delay = timeofday() + (double)time; + curr_thread->wait_for = WAIT_TIME; + rb_thread_schedule(); + } +} + +void rb_thread_polling() { if (curr_thread != curr_thread->next) { diff -u -r ruby-1.8.7-p72/intern.h ruby-1.8.7-p72-mod/intern.h --- ruby-1.8.7-p72/intern.h 2008-07-06 20:29:28.000000000 -0700 +++ ruby-1.8.7-p72-mod/intern.h 2009-01-13 00:49:01.000000000 -0800 @@ -215,6 +215,7 @@ void rb_thread_fd_close _((int)); int rb_thread_alone _((void)); void rb_thread_polling _((void)); +void rb_thread_polling_at _((double)); void rb_thread_sleep _((int)); void rb_thread_sleep_forever _((void)); VALUE rb_thread_stop _((void)); diff -u -r ruby-1.8.7-p72/process.c ruby-1.8.7-p72-mod/process.c --- ruby-1.8.7-p72/process.c 2008-06-29 02:34:43.000000000 -0700 +++ ruby-1.8.7-p72-mod/process.c 2009-01-13 00:56:32.000000000 -0800 @@ -564,6 +564,11 @@ static st_table *pid_tbl; #endif +#define USE_WAIT_QUICKSLEEP +#define WAIT_QUICKSLEEP_TIMES 10 +#define WAIT_POLL_TIME_MAX 0.06 +#define WAIT_POLL_BACKOFF_RATIO 1.3 + int rb_waitpid(pid, st, flags) int pid; @@ -573,12 +578,15 @@ int result; #ifndef NO_WAITPID int oflags = flags; + int times_through = 0; + double poll_time = 0.005; if (!rb_thread_alone()) { /* there're other threads to run */ flags |= WNOHANG; } retry: TRAP_BEG; + times_through++; #ifdef HAVE_WAITPID result = waitpid(pid, st, flags); #else /* HAVE_WAIT4 */ @@ -594,7 +602,23 @@ } if (result == 0) { if (oflags & WNOHANG) return 0; - rb_thread_polling(); +#ifdef USE_WAIT_QUICKSLEEP + // There are multiple threads enabled! + // Ok. There is a very good chance that the pid will return + // either really quickly or really slowly. So we'll do a quick + // nanosleep, then try again, then use rb_thread_polling + if(times_through < WAIT_QUICKSLEEP_TIMES) { + static struct timespec half_one_millisecond = { 0, 500000 }; + nanosleep(&half_one_millisecond, NULL); + goto retry; + } +#endif + // Back off the poll time until we exceed the max + if(poll_time < WAIT_POLL_TIME_MAX) { + poll_time *= WAIT_POLL_BACKOFF_RATIO; + } + + rb_thread_polling_at(poll_time); if (rb_thread_alone()) flags = oflags; goto retry; } --Apple-Mail-6-128149838 Content-Type: text/plain; charset=US-ASCII; format=flowed Content-Transfer-Encoding: 7bit --Apple-Mail-6-128149838--