For a dramatis actor to work with the Observable module, it needs to forward respond_to?(:update) to the proxied object. The code that handled this before has been changed (I think since you brought blankslate into it).
> -----Original Message----- > From: dramatis@googlegroups.com > [mailto:dramatis@googlegroups.com] On Behalf Of Chuck Remes > Sent: Monday, September 29, 2008 6:24 PM > To: dramatis@googlegroups.com > Subject: forwarding #respond_to? to the proxied object
> For a dramatis actor to work with the Observable module, it needs to > forward respond_to?(:update) to the proxied object. The code that > handled this before has been changed (I think since you brought > blankslate into it).
> Any idea how I can mimic this functionality now?
I just checked in observable_spec.rb that shows observable being used and seems to function reasonably.
I use actors for both the observer and the observed. I think making one an actor and not the other won't be safe and sane.
One twist is that you have to explicitly #always allow the #respond_to? before making the #add_observer call, otherwise you'll get a deadlock because observed wants to validate the observer before it returns, but observer is waiting for observed to finish and won't, well, respond to #respond_to?. You could get around this by making the add_observer call a release call. Whether that is okay or not would depend on whether other code assumed the handshake had completed before the constructor returned: with a release call, you're not sure that observed has really acknowledged the relationship, which the normal rpc method does ensure. Since it'd be nice to have the invariant that observer's #initialize didn't finish until the relationship was established, I'd stick with the #always call for now.
The other thing that would be interesting to consider would be making #notify_observers make release calls.
Since the observer probably doesn't care to wait around for the notification to complete, it makes sense to have it call out the release name, i.e., in the observed class, add something like def add_observer actor super release( actor ) end You could put in code to make it work with normal non-actor objects, too, but I don't have any sane use cases that mix actors and non-actors with Observer, so I think that might be moot.
This doesn't actually work, though, and it comes back to the #respond_to? call that observed makes. It wants to use the name from the add_observer call to both to verify that observer#update method exists (though I think this violates duck-typing) and to call it. The second case can use release semantics but the first requires rpc semantics.
My hack is
def add_observer actor release_name = release( actor ) class << release_name def respond_to? message message == :update ? true : super end end super release_name end
This is all pretty tied to the current implementation of observer and might do weird things if you try to add and remove observers dynamically.
This is an area that a full-on actor version of observer, with more interesting concurrent semantics, would certainly be interesting ... but, of course, observer already exists, so as long as it can be made work ...
I've been playing around with this today. I had trouble getting it to work (the respond_to?(:update) check was failing) so I edited the Observable module and removed that "sanity" check. (I may submit something similar to it as a patch back to core since I think that check is superfluous.)
What I see is that the asynch method call (I register the released name in #add_observer) has quite a bit of overhead. Even though I am grabbing the data, shoving it into an array and then doing the asynch call, I see about a 5 to 7 millisecond overhead for the asynch call to return. If I skip the asynch call and don't do anything, the overhead is about 300 microseconds. This was all measured on a dual quad-core system (2.66Ghz) under jruby 1.1.4 after it has had time to "warm up" and compile whatever it can compile via hotspot.
The difference between the two measurements is pretty significant. Is it spinning up a new thread every time I do an asynch call?
I looked through the docs for some controls on the thread pool size so I could put an upper limit on it but I didn't see any such control exposed. (I'm not even sure this is an issue... I'm kind of stabbing in the dark.)
So, it works but it is kind of pokey.
cr
On Sep 29, 2008, at 10:15 PM, Steven Parkes wrote:
> I just checked in observable_spec.rb that shows observable being > used and > seems to function reasonably.
> I use actors for both the observer and the observed. I think making > one an > actor and not the other won't be safe and sane.
> One twist is that you have to explicitly #always allow the > #respond_to? > before making the #add_observer call, otherwise you'll get a deadlock > because observed wants to validate the observer before it returns, but > observer is waiting for observed to finish and won't, well, respond to > #respond_to?. You could get around this by making the add_observer > call a > release call. Whether that is okay or not would depend on whether > other code > assumed the handshake had completed before the constructor returned: > with a > release call, you're not sure that observed has really acknowledged > the > relationship, which the normal rpc method does ensure. Since it'd be > nice to > have the invariant that observer's #initialize didn't finish until the > relationship was established, I'd stick with the #always call for now.
> The other thing that would be interesting to consider would be making > #notify_observers make release calls.
> Since the observer probably doesn't care to wait around for the > notification > to complete, it makes sense to have it call out the release name, > i.e., in > the observed class, add something like > def add_observer actor > super release( actor ) > end > You could put in code to make it work with normal non-actor objects, > too, > but I don't have any sane use cases that mix actors and non-actors > with > Observer, so I think that might be moot.
> This doesn't actually work, though, and it comes back to the > #respond_to? > call that observed makes. It wants to use the name from the > add_observer > call to both to verify that observer#update method exists (though I > think > this violates duck-typing) and to call it. The second case can use > release > semantics but the first requires rpc semantics.
> My hack is
> def add_observer actor > release_name = release( actor ) > class << release_name > def respond_to? message > message == :update ? true : super > end > end > super release_name > end
> This is all pretty tied to the current implementation of observer > and might > do weird things if you try to add and remove observers dynamically.
> This is an area that a full-on actor version of observer, with more > interesting concurrent semantics, would certainly be interesting ... > but, of > course, observer already exists, so as long as it can be made work ...
> I've been playing around with this today. I had trouble > getting it to > work (the respond_to?(:update) check was failing)
I still don't understand why this is the case. The specs run fine, so I'm wondering what is different.
> I see about a 5 to 7 millisecond overhead for the > asynch call to > return
So you're just timing the notify_observer call, using a release name, right? Just that one single operation?
I'll look at it. It's likely there's all sorts of room for optimization since I've been looking at functionality with clarity first.
Hmmm ... I need to look and see what, if any, support JRuby has for dtrace. MRI does, but since the thread model for MRI is green, I'm not sure how much performance data one can get out of dtrace in that context (works great for tracing messages, though.)
If I had any spare time, I'd love to port the dtrace gem to java/jruby. And a pony.
>> I've been playing around with this today. I had trouble >> getting it to >> work (the respond_to?(:update) check was failing)
> I still don't understand why this is the case. The specs run fine, > so I'm > wondering what is different.
I don't either. I now consider this a ruby bug in the Observable module, so I have "corrected" it in all my copies. I'll send a patch upstream and see if it gets accepted.
>> I see about a 5 to 7 millisecond overhead for the >> asynch call to >> return
> So you're just timing the notify_observer call, using a release > name, right? > Just that one single operation?
Well, in a word, no. But I made a mistake which may render this moot. In my earlier email I said I was using jruby 1.1.4 when in fact I was using a trunk version post-1.1.4. I backed it out to the latest release and now all the calls are in the 300 to 700 microsecond range which is *much* better. I also had some crashing under the trunk version that I do not have now (I was getting java.nio.BufferOverflowException errors when writing to a file).
I'll have to look at that a bit deeper so jruby doesn't have regressions before they cut the 1.1.5 release.
I'll also time that one specific call to see how expensive it is as an asynch call versus a synchronous call.
> I'll look at it. It's likely there's all sorts of room for > optimization > since I've been looking at functionality with clarity first.
This may not be low-hanging fruit anymore now that I went back to the 1.1.4 jruby release. If you have other areas you wanted to focus on first, it's probably safe to do so. Sorry for (almost) sending you on a wild goose chase. I'll be more careful in the future.
> Hmmm ... I need to look and see what, if any, support JRuby has for > dtrace. > MRI does, but since the thread model for MRI is green, I'm not sure > how much > performance data one can get out of dtrace in that context (works > great for > tracing messages, though.)
> If I had any spare time, I'd love to port the dtrace gem to java/ > jruby. And > a pony.
Ponies are a lot of work. May I suggest a turtle? :)
> I don't either. I now consider this a ruby bug in the Observable > module, so I have "corrected" it in all my copies. I'll send a patch > upstream and see if it gets accepted.
Yeah, the check is (arguably) a misfeature, but it should work under dramatis ... unless you're passing the release name to add_observer ... which now that I think of it, that sounds like you're doing? That definitely won't work: the observered will use that name and thus make an async call to #respond_to?, which will always return nil.
If that's the case, we at least know what's going on. I don't like mysteries. In code.
> I backed it out to the latest > release and now all the calls are in the 300 to 700 > microsecond range > which is *much* better.
Agreed. Almost a little too much better, but, then, it is only queuing the task. You mentioned a thread getting spun up, and this may or may not happen (depends on whether there are pooled threads available) but in any case, your caller doesn't do the spin-up. It's handled by the scheduler which has its own thread. Your calling thread should only awaken the scheduler, and that only if it's gone to sleep with nothing to do. Actually, you might actually start the scheduler, if it's not there yet (it gets started on demand). In any case, only the scheduler actually creates actor threads.
That said, your call/thread (at this point) does pay the cost of determining if the receiver is available to accept the message immediately: that is, it's not already executing something and the actor's selective receive gate allows it through. It needs to do this because it only awakens the scheduler if the scheduler should activate the actor. Otherwise, it just pushes the task on the actor's queue for later consideration. That gate check is one of the things I want to look at.
> I'll also time that one specific call to see how expensive it > is as an > asynch call versus a synchronous call.
Yeah. That's a different kettle of fish. A sync call includes several thread switches.
> This may not be low-hanging fruit anymore
Think of the dramatis tree as laden with low hanging fruit at this point. I'm happy to work in the areas where overripe fruit is about to fall on someone's head. Message queuing and dispatch are probably the most interesting computation parts at this point, given the complexity of selective receive.
> Ponies are a lot of work. May I suggest a turtle? :)
>> I don't either. I now consider this a ruby bug in the Observable >> module, so I have "corrected" it in all my copies. I'll send a patch >> upstream and see if it gets accepted.
> Yeah, the check is (arguably) a misfeature, but it should work under > dramatis ... unless you're passing the release name to > add_observer ... > which now that I think of it, that sounds like you're doing? That > definitely > won't work: the observered will use that name and thus make an async > call to > #respond_to?, which will always return nil.
> If that's the case, we at least know what's going on. I don't like > mysteries. In code.
Wow, I need to double check my work before I post (see below too). I was calling #add_observer with #release which is why that was failing. You nailed it in one.
>> I backed it out to the latest >> release and now all the calls are in the 300 to 700 >> microsecond range >> which is *much* better.
> Agreed. Almost a little too much better, but, then, it is only > queuing the > task.
<sigh> I did a bunch of benchmarking yesterday and today. Here are my results.
synchronous actor (not called with #release) 30 to 45 ms per call 40% CPU charge for the process
asynchronous actor (called with #release) 4 to 8 ms per call 10% CPU charge for the process
synchronous non-actor 0.2 to 15 ms per call 4% CPU charge
dummy/empty method (I commented out the call to the actor/non-actor and measured the overhead of the surrounding code) 200 to 500 microseconds
So, my last email misstated the overhead for the call to an asynch actor. The overhead of calling #update on a released actor ranges from 3.5 ms to 7.8 ms as near as I can tell (4 ms minus 500 microseconds). That's pretty heavy.
My code is invoked on average about every 50 milliseconds. Sometimes it is called as quickly as every 150 microseconds. The vast majority of these calls result in very little work, so it finished in around 200 microseconds. Every 50th (or 100th or 400th, user configurable) call, it has to do quite a bit more work. This invocation takes between 9 and 15 ms. This is the one I wanted to speed up because I don't want to miss another signal by "blocking" for 9 to 15 ms; turning it into an asynch call would allow me to return control to the event handler quicker so I wouldn't miss any transient signals.
Moving to an asynch actor slows down my most common case by an order of magnitude. It "speeds up" the less common case by allowing control to return to the program faster (4 to 8 ms versus 9 to 15 ms) but the juice ain't worth the squeeze right now. I'll need to stick to my non- actor configuration until I get a chance to look at the scheduler and look for optimizations.
> You mentioned a thread getting spun up, and this may or may not happen > (depends on whether there are pooled threads available) but in any > case, > your caller doesn't do the spin-up. It's handled by the scheduler > which has > its own thread. Your calling thread should only awaken the > scheduler, and > that only if it's gone to sleep with nothing to do. Actually, you > might > actually start the scheduler, if it's not there yet (it gets started > on > demand). In any case, only the scheduler actually creates actor > threads.
> That said, your call/thread (at this point) does pay the cost of > determining > if the receiver is available to accept the message immediately: that > is, > it's not already executing something and the actor's selective > receive gate > allows it through. It needs to do this because it only awakens the > scheduler > if the scheduler should activate the actor. Otherwise, it just > pushes the > task on the actor's queue for later consideration. That gate check > is one > of the things I want to look at.
>> I'll also time that one specific call to see how expensive it >> is as an >> asynch call versus a synchronous call.
> Yeah. That's a different kettle of fish. A sync call includes > several thread > switches.
Yes, as evidenced by the benchmark results posted above. Using an actor synchronously isn't an option for me at all.
>> This may not be low-hanging fruit anymore
> Think of the dramatis tree as laden with low hanging fruit at this > point. > I'm happy to work in the areas where overripe fruit is about to fall > on > someone's head. Message queuing and dispatch are probably the most > interesting computation parts at this point, given the complexity of > selective receive.
Looks like it still needs some TLC. I will start poking at it early next week... the remainder of this week is doing some cleanup on failing tests that have been failing for too long. Gotta get all that code tightened up again. :(
> I did a bunch of benchmarking yesterday and today. Here are > my results.
Well, this is a bit more of what I expected ...
> synchronous actor (not called with #release)
This is a very complex operation, including two message queues and a few thread context switches. It's always going to be much worse than a function call, using any thread mechanism.
> 30 to 45 ms per call > 40% CPU charge for the process
What exactly do these numbers mean? 30-45 ms is the delta of the time before and after the call? That makes sense, but what is CPU charge?
> 3.5 ms to 7.8 ms as near as I can tell (4 ms minus 500 microseconds).
So, you're talking about a factor of 8, right?
It can be improved, I'm sure, but I'm not sure how much. I don't actually know how long Java's synchronization primitives take and how well they're handled in JRuby.
But at least now we have some interesting questions for which someone cares about the answers.
>> 30 to 45 ms per call >> 40% CPU charge for the process
> What exactly do these numbers mean? 30-45 ms is the delta of the > time before > and after the call? That makes sense, but what is CPU charge?
All of these numbers are referring to the duration of the call. I saved the time before the method call and computed the duration of it.
"CPU charge" is the percentage of CPU consumed by the process as reported by top.
>> 3.5 ms to 7.8 ms as near as I can tell (4 ms minus 500 microseconds).
> So, you're talking about a factor of 8, right?
Yes.
> It can be improved, I'm sure, but I'm not sure how much. I don't > actually > know how long Java's synchronization primitives take and how well > they're > handled in JRuby.
> But at least now we have some interesting questions for which > someone cares > about the answers.
You have to be pretty careful about letting jruby warm up. With the pingpong example, it's kinda fun because it gets faster the longer it runs and since pingpong prints out a message periodically, you can see it happen.
This is on a slightly optimized version which I'll check in, but I suspect the changes aren't terribly relevant (they're about a 15% speedup on pingpong.)
With the jruby warm up, when I run an instrumented version of pingpoing, the first release call takes 2.5ms on a 2GHz Opteron. The last call (when doing 5000 volleys) takes 250us.
It's not possible to do a head-to-head comparison with the serial code since the latter is recursive. But if you step back and average the wall clock time against the number of volleys for a large number of volleys, the actor version ends up averaging less that 220us per call. That's the sum of the send, the (implicit) receive, and the task execution (which just sends another message, but that's removed by the average).
The serial code gets as low as 62us per volley on average, so there's still a factor of 4 here, which, since the pingpong tasks don't do anything, isn't that bad (if I haven't messed up my interpretation). This kind of a worst-case grain-size.
This is all with JRuby 1.1.4, Sun JDK 1.6.0.07, and giving JRuby -J-server -J-Xss512m.
As a comparison, 1.8.6 can get the serial code as low as 13us per volley. The actor version runs at about 315us per volley (everybody seems to know 1.8.6 threads have some real limitations ...)
Under (some version of) 1.9, the serial code gets as low as 6us per volley and the actor version, 100us per volley.
> -----Original Message----- > From: dramatis@googlegroups.com > [mailto:dramatis@googlegroups.com] On Behalf Of Chuck Remes > Sent: Thursday, October 02, 2008 5:50 PM > To: dramatis@googlegroups.com > Subject: Re: forwarding #respond_to? to the proxied object
> On Oct 2, 2008, at 7:13 PM, Steven Parkes wrote:
> >> 30 to 45 ms per call > >> 40% CPU charge for the process
> > What exactly do these numbers mean? 30-45 ms is the delta of the > > time before > > and after the call? That makes sense, but what is CPU charge?
> All of these numbers are referring to the duration of the call. I > saved the time before the method call and computed the duration of it.
> "CPU charge" is the percentage of CPU consumed by the process as > reported by top.
> >> 3.5 ms to 7.8 ms as near as I can tell (4 ms minus 500 > microseconds).
> > So, you're talking about a factor of 8, right?
> Yes.
> > It can be improved, I'm sure, but I'm not sure how much. I don't > > actually > > know how long Java's synchronization primitives take and how well > > they're > > handled in JRuby.
> > But at least now we have some interesting questions for which > > someone cares > > about the answers.