Skip to content

Instantly share code, notes, and snippets.

@headius
Created December 3, 2010 23:01
Show Gist options
  • Save headius/727684 to your computer and use it in GitHub Desktop.
Save headius/727684 to your computer and use it in GitHub Desktop.
A GIL for JRuby?
~/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
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>>();
@tarcieri
Copy link

tarcieri commented Dec 3, 2010

A SORELY NEEDED FEATURE!#$!#$!$#!$!#

@ELLIOTTCABLE
Copy link

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