I’m running the latest from jruby-1_6, and rails 3.0.9 under tomcat 7.
I’m seeing the following lock, which a bunch of other threads are
waiting on.
“http-nio-8080-exec-130” daemon prio=10 tid=0x00007ff7894a0000
nid=0x4798 waiting for monitor entry [0x00007ff772ee7000]
java.lang.Thread.State: BLOCKED (on object monitor)
at
org.jruby.RubyBasicObject.getObjectId(RubyBasicObject.java:1005)
- locked <0x00000005e32121d0> (a org.jruby.RubyObject)
at org.jruby.RubyBasicObject.id(RubyBasicObject.java:993)
at org.jruby.RubyKernel.id(RubyKernel.java:1885)
at
org.jruby.RubyKernel$s$0$0$id.call(RubyKernel$s$0$0$id.gen:65535)
at
org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:133)
at
rubyjit.Module#instrumenter_BC6D93C9B6178F7575A4F7A8E8E9CAA45CB2250E.file(/home/tomcat/tomcat10/webapps/ROOT/WEB-INF/gems/gems/activesupport-3.0.9/lib/active_support/notifications.rb:74)
at
rubyjit.Module#instrumenter_BC6D93C9B6178F7575A4F7A8E8E9CAA45CB2250E.file(/home/tomcat/tomcat10/webapps/ROOT/WEB-INF/gems/gems/activesupport-3.0.9/lib/active_support/notifications.rb)
This code creates a global notifier, and calls object_id on it in all
the other threads, which appears to cause a lock when looking up the
object id. I imagine this is the right thing to do. Should I file a
bug against rails on this?
block_given? }
yield payload if block_given?
else
yield payload if block_given?
end
end
def subscribe(*args, &block)
notifier.subscribe(*args, &block).tap do
@instrumenters.clear
end
end
def unsubscribe(*args)
notifier.unsubscribe(*args)
@instrumenters.clear
end
def notifier
@notifier ||= Fanout.new
end
def instrumenter
Thread.current[:"instrumentation_#{notifier.object_id}"] ||=
object_id in MRI is mostly free, because they basically just return
the object’s pointer address in memory. That means they’re not really guaranteeing it’s unique…it’s only unique among live
objects.
In JRuby, we have to at least attempt to make it unique, but we don’t
have any unique identifier per-object provided by the JVM…so we use
a monotonically-increasing long to represent object_id. It’s not
guaranteed to be unique, but you’ll need to ask for something like
2^62 object_id’s for it to roll over (signed long / 2 for the
even-number Fixnum id’s). So that’s one lock to guarantee it’s
monotonically increasing, but I can (and will) replace that with an
AtomicLong.
Because object_id is rarely requested, we also lazily allocate space
for it on the object, stuffing it into the same table where we keep
instance variable values. That’s the second lock involved,
guaranteeing that we’re allocating one and only one slot for object_id
on this class of objects. That I think I can improve via some
double-checking trickery.
However, the bottom line is that in any modern VM, there is no unique
ID attached to every object unless we actively attach it, and so
object_id and its ilk are certainly not free and generally a lot
slower than MRI. So it’s not a great idea for them to be used heavily
on hot code paths.
If it’s possible to patch these uses of object_id to something else
without negatively impacting them, I’d say to go for it. I will also
try to reduce the cost of querying and allocating an object_id for an
object. But we’re never going to be able to make it as fast as MRI’s
ultra cheat of using a pointer, so a combination of fixes will be
necessary.
I’ve pushed to master@2c88a5b and jruby-1_6@a9e0ec1 fixes that defer
synchronization until it’s absolutely necessary. The performance
results are pretty impressive, especially in the contended case:
BEFORE:
user system
total real
1M calls to obj.object_id 0.131000 0.000000
0.131000 ( 0.131000)
1M calls to Object.new.object_id 0.232000 0.000000
0.232000 ( 0.232000)
1M * 8 threaded calls to obj.object_id 3.896000 0.000000
3.896000 ( 3.896000)
AFTER:
user system
total real
1M calls to obj.object_id 0.080000 0.000000
0.080000 ( 0.080000)
1M calls to Object.new.object_id 0.178000 0.000000
0.178000 ( 0.178000)
1M * 8 threaded calls to obj.object_id 0.465000 0.000000
0.465000 ( 0.465000)
It’s obvious the unconditional thread acquisition was causing a problem.
Charlie
On Sun, Feb 19, 2012 at 12:14 AM, Charles Oliver N.
There was another unconditional synchronization in
RubyClass.getObjectIdAccessorForWrite(). It only really needed to
synchronize when the accessor had not been allocated for that ruby
class.
I’m running some load tests this weekend against jruby-1_6. Turns out
that quite a few little changes since 1.6.5 (what we were using) had a
major impact. Particularly in the socket/select code. In 1.6.5 I had
a lot of thread blocking there that’s now gone.
That’s excellent. Please keep tossing us any lock contention issues you
see!
Being that the jruby team is really responsive on things, I think we
are just going to cautiously track jruby-1_6 for our production code.
I’ve forked the repo so we can tag 1_6 whenever there is something we
need, or if we find and fix some things ourselves we already have
things setup to do a pull request.
That’s probably safe. jruby-1_6 branch is intended to be really
stable. That’s not to say we don’t occasionally break things, but we
have a lot of safeguards to keep it from staying that way for long.
I’m running some load tests this weekend against jruby-1_6. Turns out
that quite a few little changes since 1.6.5 (what we were using) had a
major impact. Particularly in the socket/select code. In 1.6.5 I had
a lot of thread blocking there that’s now gone.
Being that the jruby team is really responsive on things, I think we
are just going to cautiously track jruby-1_6 for our production code.
I’ve forked the repo so we can tag 1_6 whenever there is something we
need, or if we find and fix some things ourselves we already have
things setup to do a pull request.
Chris
On Sun, Feb 19, 2012 at 7:16 AM, Charles Oliver N.
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.