On Sat, May 9, 2009 at 11:06 AM, Thomas E Enebo <tom.en...@gmail.com> wrote:
> On Fri, May 8, 2009 at 10:47 PM, Charles Oliver Nutter
> <charles.nut...@sun.com> wrote:
>> Subramanya Sastry wrote:
>>>
>>> I may have been wrong.   I had a chance to think through this a little bit
>>> more.
>>>
>>> Consider this ruby code:
>>>
>>> i = 5
>>> v1 = i + 1
>>> some_random_method_call()
>>> v2 = i + 1
>>>
>>> In this code snippet, the second '+' might not get optimized because
>>> 'some_random_method_call' could monkeypatch Fixnum.+ in the course of
>>> whatever it does.  This means that calls form hard walls beyond which you
>>> cannot hoist method and class modification guards (unless of course, you can
>>> precisely determine the set of methods any given call can modify -- for
>>> example, that 'some_random_method_call' will not modify Fixnum.+).
>>
>> This is certainly a challenge for entirely eliminating guards, and I believe
>> even the fastest Ruby implementations currently are unable to remove those
>> guards completely.
>>
>> In this example, if we can at least prove that i, v1, and v2 will always be
>> Fixnum when Fixnum#+ has not been replaced, we can emit guarded optimized
>> versions alongside deoptimized versions. Because Fixnum#+ falls on the
>> "rarely or never replaced" end of the spectrum, reducing + operations to a
>> simple type guard plus "Fixnum has been modified" guard is probably the best
>> we can do without code replacement. More on code replacement thoughts later.
>>
>>> So, this is somewhat similar to pointer alias analysis in C programs.  In
>>> C programs, the ability to pass around pointers and manipulate them in an
>>> unrestricted fashion effectively restricts the kind of optimizations that a
>>> compiler can do.  Similarly, in Ruby programs, it seems to me that the
>>> ability for code to arbitrarily modify code effectively restricts the kind
>>> of optimizations that a compiler can do.  Open classes and eval and all of
>>> that are to Ruby what pointers are to C.  Potentially powerful for the
>>> programmer, but hell for the compiler.  More on this further below after
>>> discussion of the multi-threaded scenario.
>>
>> It is true that we generally must assume code modification events will have
>> a large impact on core methods. This means we need to always guard, even if
>> such modifications are exceedingly rare. However it may be pessimistic to
>> assume such guards will be unacceptably slow in comparison to the rest of
>> the system, when this may or may not be the case. Currently, all method
>> calls in normal execution scenarios must read a volatile field for the call
>> site invalidation guard. Although it's difficult to measure accurately, the
>> volatile guard should in theory be a very large expense, and John Rose said
>> as much when I described our current system to him. But it may be possible
>> to make such guards non-volatile if we have other mechanisms for triggering
>> memory synchronization that those non-volatile calls could see.
>>
>> One theory I had was that since we periodically ping another volatile field
>> for cross-thread events (kill, raise), there may be a way we can rely on
>> those events to trigger our memory sync. But I am unfamiliar enough with the
>> Java memory model that I do not know if such a volatility-reducing mechanism
>> would work or be reliable.
>>
>>> If thread 2 modifies Fixnum.+, you might expect the modification to be
>>> reflected in thread 1 at some point.  If you hoist method/class guards all
>>> the way outside the loop and convert the i+1 to integer addition, code
>>> modifications from thread 2 won't propagate to thread 1.
>>> But, in this scenario, I would argue that any code that relies on behavior
>>> like this is broken.  This is effectively a race condition and different
>>> ruby implementations will yield different behavior.  In fact, code
>>> modification by meta-programming is effectively modifying class meta-data.
>>>  So, concurrent code modification is not very different from concurrent
>>> writes to program data.   I am not sure if the meta programming model treats
>>> modification to open classes as changes to a program-visible meta-data.  If
>>> it were, then programmer can then synchronize on that meta-data object
>>> before patching code.  But, I expect this is not the case because that would
>>> then force every method call to acquire a lock on the class meta-data.
>>
>> Again there's some unknowns here, but the likelihood of method table
>> modifications happening across threads (or at least the likelihood of those
>> changes producing runtime behavioral effects across threads) is probably
>> very low. The majority of Ruby libraries and frameworks have either been
>> designed with single threads in mind--i.e. the expectation is that a given
>> thread would exit the library before a code modification would happen, if it
>> ever did--or have been designed with multi-threading in mind--making code
>> modifications at boot time or with an expectation that only one thread would
>> ever see them (like modifying a thread-local object to add new methods or
>> modules). Both cases have few expectations about vigorous or even common
>> cross-thread code modification events.
>>
>>> Where does this leave us?  Consider a Rails app where there is a
>>> UpdateCodeController with a method called load_new_code(C, m) which
>>> basically updates running code with a new version of a method 'm' in class
>>> 'C' (lets say a bug fix).  This discussion of code modification then boils
>>> down to asking the question: at what point does the new method become
>>> available?  In the absence of code modification synchronization (on C's
>>> metadata object), the ruby implementation can block on this request while
>>> continuing to run the old version of 'm' till it is convenient to switch
>>> over to new code!
>>>
>>> This is obviously a contrived example, but the point of this is: If there
>>> is no mechanism for the programmer to force code modifications to be visible
>>> in a ruby implementation, the ruby implementation has flexibility over how
>>> long it turns a blind eye to code modifications in a thread other than the
>>> currently executing thread.  So, what you need to worry about is figuring
>>> out the hard boundaries for method and class guards assuming a
>>> single-threaded program and optimizing for that scenario.
>>>
>>> Am I missing something?
>>
>> No, I think this is right on. And to cement this even further: we are
>> blazing a very new trail when it comes to parallel-executing Ruby, which
>> means we may be able to *set* expectations for cross-thread
>> code-modification behavior. A user moving their library to JRuby will then
>> (as now) have to pay a bit more attention to how that library behaves in a
>> (probably) more formal and (definitely) better-specified threading
>> environment.
>>
>>> Now, back to the single-threaded scenario and the earlier example:
>>>
>>> i = 5
>>> v1 = i + 1
>>> some_random_method_call()
>>> v2 = i + 1
>>>
>>> As discussed earlier, the problem here is determining the set of methods
>>> that a call can modify, and this is somewhat similar to the C pointer
>>> aliasing problem.  That by itself is "not a big deal" because unlike C
>>> pointers, code modification is much rarer.  So, in the ruby implementation,
>>> you could optimistically / speculatively optimize a ton of code assuming NO
>>> code modifications and move the burden of invalidation from call sites
>>> (which are everywhere) to the centralized class meta-data structures i.e.
>>> you install listeners/traps/callbacks (whatever you want to call them) on
>>> the class and when a method is modified, you invalidate all optimized code
>>> that assumes that the method is "static".  This is non-trivial (will require
>>> on-stack replacement of code), but at least conceivable.  But, since you are
>>> compiling for the JVM (or LLVM in the case of MacRuby), once you JIT, you
>>> effectively cede control.  You dont have a mechanism to invalidate JIT-ted
>>> code.  So, at this time, I am not sure if you can really get around this
>>> (extremely pessimistic) constraint.  Only ruby implementations that can
>>> control the compilation stack all the way to machine code (or have hooks to
>>> invalidate / modify existing code) might be able to get around this.  I am
>>> curious how MacRuby-LLVM combination is tackling this ...
>>
>> As early as last fall, JRuby's call site invalidation was "active" as you
>> describe, and a list of call sites attached to a method object were
>> physically flushed when changes occurred in that method's "home" hierarchy
>> likely to render such caching invalid. This approach was originally
>> abandoned due to the complexity of ensuring multiple threads reaching the
>> same call site at the same time would not accidentally miss an invalidation
>> event and cache bad code forever. This is also, in fact, an open question
>> about the invokedynamic active invalidation; when I and others brought it up
>> to John Rose, he recognized that most dynamic calls would still need to have
>> "passive" invalidation guards even after linking. The original call site
>> could still be actively invalidated, but without introducing locks at call
>> sites (sure performance death) there's no simple way to avoid "just in case"
>> passive guards as well.
>
>  This makes me wonder if we cannot actually ascertain the cost of
> various locking/volatile/active/inactive scenarios.  Of course in a
> pet micro bench of these it may give an unrealistic answer, but it
> would still be cool to get some understanding of cost.
>
>  It also makes me think of more exotic solutions like generating a
> callsite class per thread and per call then using actual
> synchronization since then you would get monomorphic call and
> lightweight synch lock.  I wonder if anyone has ever discussed all the
> strategies that are realistic?  Like is that realistic or the suck :)

HEH. I guess my above scenario would only work if AST was per thread
too :)  but the overarching question is still there....it would be
nice to have some quantitative mechanism for evaluating this stuff...

-Tom

-- 
Blog: http://www.bloglines.com/blog/ThomasEEnebo
Email: en...@acm.org , tom.en...@gmail.com

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply via email to