Created
December 3, 2010 23:01
-
-
Save headius/727684 to your computer and use it in GitHub Desktop.
A GIL for JRuby?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
~/projects/jruby ➔ # single thread performance | |
~/projects/jruby ➔ jruby --server bench/bench_threaded_reverse.rb 1 | |
... | |
concurrency is - 1 | |
started thread 0 | |
Thread 0 running | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
another 10 in #<Thread:0xc8c7d6 run> | |
Thread 0 done | |
Time: 4.473 | |
~/projects/jruby ➔ # concurrent multi-threaded performance | |
~/projects/jruby ➔ jruby --server bench/bench_threaded_reverse.rb 2 | |
... | |
concurrency is - 2 | |
started thread 0 | |
started thread 1 | |
Thread 1 running | |
another 10 in #<Thread:0x11abd68 run> | |
Thread 0 running | |
another 10 in #<Thread:0x16d3536 run> | |
another 10 in #<Thread:0x11abd68 run> | |
another 10 in #<Thread:0x16d3536 run> | |
another 10 in #<Thread:0x16d3536 run> | |
another 10 in #<Thread:0x11abd68 run> | |
another 10 in #<Thread:0x16d3536 run> | |
another 10 in #<Thread:0x11abd68 run> | |
another 10 in #<Thread:0x16d3536 run> | |
another 10 in #<Thread:0x11abd68 run> | |
another 10 in #<Thread:0x16d3536 run> | |
another 10 in #<Thread:0x11abd68 run> | |
Thread 0 done | |
Thread 1 done | |
Time: 2.906 | |
~/projects/jruby ➔ # GIL (nonconcurrent) multi-threaded performance | |
~/projects/jruby ➔ jruby -Xthread.gil=true --server bench/bench_threaded_reverse.rb 2 | |
... | |
concurrency is - 2 | |
started thread 0 | |
started thread 1 | |
Thread 1 running | |
another 10 in #<Thread:0x10bbd42 run> | |
Thread 0 running | |
another 10 in #<Thread:0x498713 run> | |
another 10 in #<Thread:0x10bbd42 run> | |
another 10 in #<Thread:0x498713 run> | |
another 10 in #<Thread:0x10bbd42 run> | |
another 10 in #<Thread:0x498713 run> | |
another 10 in #<Thread:0x10bbd42 run> | |
another 10 in #<Thread:0x498713 run> | |
another 10 in #<Thread:0x498713 run> | |
another 10 in #<Thread:0x10bbd42 run> | |
another 10 in #<Thread:0x498713 run> | |
another 10 in #<Thread:0x10bbd42 run> | |
Thread 0 done | |
Thread 1 done | |
Time: 4.964 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/src/org/jruby/Ruby.java b/src/org/jruby/Ruby.java | |
index c3083d8..4d71277 100644 | |
--- a/src/org/jruby/Ruby.java | |
+++ b/src/org/jruby/Ruby.java | |
@@ -373,56 +373,62 @@ public final class Ruby { | |
* and $0 ruby global variables. | |
*/ | |
public void runFromMain(InputStream inputStream, String filename) { | |
- IAccessor d = new ValueAccessor(newString(filename)); | |
- getGlobalVariables().define("$PROGRAM_NAME", d); | |
- getGlobalVariables().define("$0", d); | |
- | |
- for (Iterator i = config.getOptionGlobals().entrySet().iterator(); i.hasNext();) { | |
- Map.Entry entry = (Map.Entry) i.next(); | |
- Object value = entry.getValue(); | |
- IRubyObject varvalue; | |
- if (value != null) { | |
- varvalue = newString(value.toString()); | |
- } else { | |
- varvalue = getTrue(); | |
+ threadService.acquireGIL(); | |
+ | |
+ try { | |
+ IAccessor d = new ValueAccessor(newString(filename)); | |
+ getGlobalVariables().define("$PROGRAM_NAME", d); | |
+ getGlobalVariables().define("$0", d); | |
+ | |
+ for (Iterator i = config.getOptionGlobals().entrySet().iterator(); i.hasNext();) { | |
+ Map.Entry entry = (Map.Entry) i.next(); | |
+ Object value = entry.getValue(); | |
+ IRubyObject varvalue; | |
+ if (value != null) { | |
+ varvalue = newString(value.toString()); | |
+ } else { | |
+ varvalue = getTrue(); | |
+ } | |
+ getGlobalVariables().set("$" + entry.getKey().toString(), varvalue); | |
} | |
- getGlobalVariables().set("$" + entry.getKey().toString(), varvalue); | |
- } | |
- if (filename.endsWith(".class")) { | |
- // we are presumably running a precompiled class; load directly | |
- Script script = CompiledScriptLoader.loadScriptFromFile(this, inputStream, filename); | |
- if (script == null) { | |
- throw new MainExitException(1, "error: .class file specified is not a compiled JRuby script"); | |
+ if (filename.endsWith(".class")) { | |
+ // we are presumably running a precompiled class; load directly | |
+ Script script = CompiledScriptLoader.loadScriptFromFile(this, inputStream, filename); | |
+ if (script == null) { | |
+ throw new MainExitException(1, "error: .class file specified is not a compiled JRuby script"); | |
+ } | |
+ script.setFilename(filename); | |
+ runScript(script); | |
+ return; | |
} | |
- script.setFilename(filename); | |
- runScript(script); | |
- return; | |
- } | |
- | |
- Node scriptNode = parseFromMain(inputStream, filename); | |
- // done with the stream, shut it down | |
- try {inputStream.close();} catch (IOException ioe) {} | |
+ Node scriptNode = parseFromMain(inputStream, filename); | |
- ThreadContext context = getCurrentContext(); | |
+ // done with the stream, shut it down | |
+ try {inputStream.close();} catch (IOException ioe) {} | |
- String oldFile = context.getFile(); | |
- int oldLine = context.getLine(); | |
- try { | |
- context.setFileAndLine(scriptNode.getPosition()); | |
+ ThreadContext context = getCurrentContext(); | |
- if (config.isAssumePrinting() || config.isAssumeLoop()) { | |
- runWithGetsLoop(scriptNode, config.isAssumePrinting(), config.isProcessLineEnds(), | |
- config.isSplit()); | |
- } else { | |
- runNormally(scriptNode); | |
+ String oldFile = context.getFile(); | |
+ int oldLine = context.getLine(); | |
+ try { | |
+ context.setFileAndLine(scriptNode.getPosition()); | |
+ | |
+ if (config.isAssumePrinting() || config.isAssumeLoop()) { | |
+ runWithGetsLoop(scriptNode, config.isAssumePrinting(), config.isProcessLineEnds(), | |
+ config.isSplit()); | |
+ } else { | |
+ runNormally(scriptNode); | |
+ } | |
+ } finally { | |
+ context.setFileAndLine(oldFile, oldLine); | |
+ if (config.isProfiling()) { | |
+ context.getProfileData().printProfile(context, profiledNames, profiledMethods, System.out); | |
+ } | |
} | |
} finally { | |
- context.setFileAndLine(oldFile, oldLine); | |
- if (config.isProfiling()) { | |
- context.getProfileData().printProfile(context, profiledNames, profiledMethods, System.out); | |
- } | |
+ threadService.releaseGIL(); | |
} | |
} | |
diff --git a/src/org/jruby/RubyThread.java b/src/org/jruby/RubyThread.java | |
index 916c781..6468f25 100644 | |
--- a/src/org/jruby/RubyThread.java | |
+++ b/src/org/jruby/RubyThread.java | |
@@ -286,9 +286,8 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
runtime.getThreadService().associateThread(thread, this); | |
} | |
- // We yield here to hopefully permit the target thread to schedule | |
- // MRI immediately schedules it, so this is close but not exact | |
- Thread.yield(); | |
+ runtime.getThreadService().releaseGIL(); | |
+ runtime.getThreadService().acquireGIL(); | |
return this; | |
} catch (SecurityException ex) { | |
@@ -322,6 +321,8 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
} | |
public void pollThreadEvents(ThreadContext context) { | |
+ getRuntime().getThreadService().releaseGIL(); | |
+ getRuntime().getThreadService().acquireGIL(); | |
if (mail != null) checkMail(context); | |
} | |
@@ -495,6 +496,7 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
// We need this loop in order to be able to "unblock" the | |
// join call without actually calling interrupt. | |
long start = System.currentTimeMillis(); | |
+ getRuntime().getThreadService().releaseGIL(); | |
while(true) { | |
currentThread.pollThreadEvents(); | |
threadImpl.join(timeToWait); | |
@@ -511,6 +513,8 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
} catch (ExecutionException ie) { | |
ie.printStackTrace(); | |
assert false : ie; | |
+ } finally { | |
+ getRuntime().getThreadService().acquireGIL(); | |
} | |
if (exitingException != null) { | |
@@ -638,6 +642,8 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
status = Status.RUN; | |
notifyAll(); | |
+ | |
+ getRuntime().getThreadService().acquireGIL(); | |
return this; | |
} | |
@@ -758,9 +764,11 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
pollThreadEvents(); | |
try { | |
status = Status.SLEEP; | |
+ getRuntime().getThreadService().releaseGIL(); | |
wait(millis); | |
} finally { | |
result = (status != Status.RUN); | |
+ getRuntime().getThreadService().acquireGIL(); | |
pollThreadEvents(); | |
status = Status.RUN; | |
} | |
@@ -815,10 +823,12 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
try { | |
currentBlockingTask = task; | |
pollThreadEvents(); | |
+ getRuntime().getThreadService().releaseGIL(); | |
task.run(); | |
} finally { | |
exitSleep(); | |
currentBlockingTask = null; | |
+ getRuntime().getThreadService().acquireGIL(); | |
pollThreadEvents(); | |
} | |
} | |
@@ -1025,6 +1035,7 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
try { | |
io.addBlockingThread(this); | |
blockingIO = BlockingIO.newCondition(channel, ops); | |
+ getRuntime().getThreadService().releaseGIL(); | |
boolean ready = blockingIO.await(); | |
// check for thread events, in case we've been woken up to die | |
@@ -1038,16 +1049,25 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
} finally { | |
blockingIO = null; | |
io.removeBlockingThread(this); | |
+ getRuntime().getThreadService().acquireGIL(); | |
} | |
} | |
public void beforeBlockingCall() { | |
- pollThreadEvents(); | |
+ try { | |
+ pollThreadEvents(); | |
+ } finally { | |
+ getRuntime().getThreadService().releaseGIL(); | |
+ } | |
enterSleep(); | |
} | |
public void afterBlockingCall() { | |
exitSleep(); | |
- pollThreadEvents(); | |
+ try { | |
+ pollThreadEvents(); | |
+ } finally { | |
+ getRuntime().getThreadService().acquireGIL(); | |
+ } | |
} | |
private void receivedAnException(ThreadContext context, IRubyObject exception) { | |
@@ -1065,12 +1085,22 @@ public class RubyThread extends RubyObject implements ExecutionContext { | |
if (delay_ns > 0) { | |
long delay_ms = delay_ns / 1000000; | |
int delay_ns_remainder = (int)( delay_ns % 1000000 ); | |
- executeBlockingTask(new SleepTask(o, delay_ms, delay_ns_remainder)); | |
+ getRuntime().getThreadService().releaseGIL(); | |
+ try { | |
+ executeBlockingTask(new SleepTask(o, delay_ms, delay_ns_remainder)); | |
+ } finally { | |
+ getRuntime().getThreadService().acquireGIL(); | |
+ } | |
} | |
long end_ns = System.nanoTime(); | |
return ( end_ns - start_ns ) <= delay_ns; | |
} else { | |
- executeBlockingTask(new SleepTask(o, 0, 0)); | |
+ getRuntime().getThreadService().releaseGIL(); | |
+ try { | |
+ executeBlockingTask(new SleepTask(o, 0, 0)); | |
+ } finally { | |
+ getRuntime().getThreadService().acquireGIL(); | |
+ } | |
return true; | |
} | |
} | |
diff --git a/src/org/jruby/internal/runtime/RubyRunnable.java b/src/org/jruby/internal/runtime/RubyRunnable.java | |
index 0fafab7..cd03111 100644 | |
--- a/src/org/jruby/internal/runtime/RubyRunnable.java | |
+++ b/src/org/jruby/internal/runtime/RubyRunnable.java | |
@@ -91,6 +91,7 @@ public class RubyRunnable implements Runnable { | |
try { | |
// Call the thread's code | |
try { | |
+ runtime.getThreadService().acquireGIL(); | |
IRubyObject result = proc.call(context, arguments); | |
rubyThread.cleanTerminate(result); | |
} catch (JumpException.ReturnJump rj) { | |
@@ -116,6 +117,7 @@ public class RubyRunnable implements Runnable { | |
System.err.println("WARNING: Security restrictions disallowed setting context classloader for Ruby threads."); | |
} | |
} | |
+ runtime.getThreadService().releaseGIL(); | |
} | |
} catch (ThreadKill tk) { | |
// be dead | |
diff --git a/src/org/jruby/internal/runtime/ThreadService.java b/src/org/jruby/internal/runtime/ThreadService.java | |
index d3711cb..1066ccc 100644 | |
--- a/src/org/jruby/internal/runtime/ThreadService.java | |
+++ b/src/org/jruby/internal/runtime/ThreadService.java | |
@@ -40,10 +40,13 @@ import java.util.Map; | |
import java.util.WeakHashMap; | |
import java.util.concurrent.Future; | |
+import java.util.concurrent.Semaphore; | |
+import java.util.concurrent.SynchronousQueue; | |
import org.jruby.Ruby; | |
import org.jruby.RubyThread; | |
import org.jruby.runtime.builtin.IRubyObject; | |
import org.jruby.runtime.ThreadContext; | |
+import org.jruby.util.SafePropertyAccessor; | |
/** | |
* ThreadService maintains lists ofall the JRuby-specific thread data structures | |
@@ -137,6 +140,25 @@ public class ThreadService { | |
private final ReentrantLock criticalLock = new ReentrantLock(); | |
+ private final Semaphore gil = new Semaphore(1, true); | |
+ | |
+ private static final boolean USE_GIL = SafePropertyAccessor.getBoolean("jruby.thread.gil", false); | |
+ | |
+ public void acquireGIL() { | |
+ if (USE_GIL) { | |
+ try { | |
+ gil.acquire(); | |
+ } catch (InterruptedException e) { | |
+ } | |
+ } | |
+ } | |
+ | |
+ public void releaseGIL() { | |
+ if (USE_GIL) { | |
+ gil.release(); | |
+ } | |
+ } | |
+ | |
public ThreadService(Ruby runtime) { | |
this.runtime = runtime; | |
this.localContext = new ThreadLocal<SoftReference<ThreadContext>>(); |
DUDE WHY IS THIS NOT IN THE TRUNK YET I DO NOT UNDERSTAND HOW DO I RUBY WITHOUT GIL
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A SORELY NEEDED FEATURE!#$!#$!$#!$!#