here's something I've been working under the covers for the past some
time. (You can tell I'm nuts about it as it's 1:30AM and I'm actually on
my vacation in Palma de Mallorca right now and have burned the last few
days on this).
The rest of this announcement is almost verbatim equivalent to the
overview.html in the stuff's Javadoc:
It is a library that aims to make it possible for objects created by a
runtime for one dynamic language to be passed to and manipulated by a
different dynamic language, running within the same JVM.
Basically, it provides a unified mechanism that all dynamic languages on
the JVM can use to interoperably manipulate their own objects as well as
objects from any other language that also uses this mechanism.
It strives to achieve this goal not by defining "One True Object", a
single interface to be implemented by all Java objects that represent
objects of the dynamic languages in the JVM (as is apparently the case
with .Net's Dynamic Language Runtime, see
<http://blogs.msdn.com/hugunin/archive/2007/05/02/the-one-true-object-part-1.aspx>),
but rather by providing an extensible metaobject protocol (MOP) mechanism,
as first formulated in the "In-process cross-language object interaction:
adapters or navigators?" article at
<http://www.szegedi.org/articles/wrappersOrNavigators.html>. The idea is
to completely throw away the idea of a dedicated interface on objects
themselves, and having to wrap foreign objects into adapter objects that
expose the expected interface. Instead, the operations on objects
themselves are encapsulated behind MOP objects (you can also think about
them as being the "navigators" and "manipulators" for the objects they
know about), with usually only a single MOP required per language
implementation.
The basic usage difference is that a dynamic language runtime will
retrieve a property on an object not by using code like this:
((MyDynamicInterface)obj).get("foo"); but rather it will use a MOP:
metaobjectProtocol.get(obj, "foo");
The real strength of the approach lies in extensibility and pluggability
of MOPs. MOPs can be easily composed, so if you compose your language's
native MOP with other languages' MOPs, your code will be able to natively
manipulate those other languages' objects. You can have a MOP for DOM
objects as well to natively add XML manipulating abilities into your
languages. Finally, a JavaBeans MOP is provided as standard component in
the library - you can add it as the last element of your composite MOP so
that your language can manipulate any POJO directly if no other MOP
recognizes the object. Full range of Java amenities, including optimized
runtime resolution of overloaded methods and support for JDK 1.5 variable
arguments is available.
The secret of composability is MOPs ability to declare it has no authority
to perform an operation on an object. When an operation is attempted in a
composite MOP, all its element MOPs will be attempted in turn while they
claim no authority for it. Only when one MOP either performs the operation
or authoritatively decides it must fail is the operation outcome final.
Note that the authority declaration is very fine-grained: it is per
operation and per object. It is therefore much more versatile than the
wrapper interfaces approach: when using interfaces, a wrapper class must
decide up-front which interfaces to implement on the object. A MOP can
decide based on the actual state of the object whether it can perform an
operation. This can also be handily used to extend any objects with
language specific meta-properties. Since the API allows any object, not
just strings, to be used as property identifiers, it is easy to use
implementation-private objects as property IDs for things like ECMAScript
"prototype" property and similar. A theoretical ECMAScript MOP would thus
answer the get and put requests for the prototype property of a POJO, but
would claim no authority for get and put requests for other properties,
letting those fall through to the JavaBeans MOP.
State of the art
----------------
This library is to be considered highly volatile in its current state.
Input from Java Dynamic Language community can drive much of its further
design. The JavaBeans MOP in particular will likely acquire few additions,
at least the ability to operate on native Java arrays, if not on the Java
collection types (those might actually be better placed in a different
MOP). The overloaded method resolution currently doesn't use type bounds
for generic types (in case an overload has generic types), but it is not
clear at the moment whether it should at all use them (probably not),
since at runtime all method argument types are already concrete classes.
Also, an automatic discovery mechanism using the JAR file service
specification mechanism will be added that dynamic language runtimes can
use to automatically create composite MOPs from all available MOPs in the
classpath.
Developer resources
-------------------
The project is hosted at SourceForge, under
<http://www.sf.net/projects/dynalang>. You can use SVN to check out the
code from there.
I also own the dynalang.org domain, although no content is hosted there
yet.
If you check out the latest version from SVN, try running ant test. This
produces both a JUnit test report at build/test/report/index.html and test
coverage report (generated using EMMA) at
build/coverage/report/index.html. To build the JavaDoc, use ant javadoc,
and then open build/javadoc/index.html.
All code is Apache 2.0 licensed.
Attila.
It seems like we're all trending toward the same state; the JRuby team
is also considering and experimenting with a MOP for at least Java types
and potentially for Ruby core types (to allow us to use java.lang.Long
directly as a Ruby Fixnum, for example).
It also seems like work on a common MOP that could be reused across
languages would be extremely useful, and we've all basically got the
same requirements to meet when calling Java code.
>
> The real strength of the approach lies in extensibility and pluggability
> of MOPs. MOPs can be easily composed, so if you compose your language's
> native MOP with other languages' MOPs, your code will be able to natively
> manipulate those other languages' objects. You can have a MOP for DOM
> objects as well to natively add XML manipulating abilities into your
> languages. Finally, a JavaBeans MOP is provided as standard component in
> the library - you can add it as the last element of your composite MOP so
> that your language can manipulate any POJO directly if no other MOP
> recognizes the object. Full range of Java amenities, including optimized
> runtime resolution of overloaded methods and support for JDK 1.5 variable
> arguments is available.
This is very interesting, and could be the key to allowing multiple
languages to use the same MOP library.
> The secret of composability is MOPs ability to declare it has no authority
> to perform an operation on an object. When an operation is attempted in a
> composite MOP, all its element MOPs will be attempted in turn while they
> claim no authority for it. Only when one MOP either performs the operation
> or authoritatively decides it must fail is the operation outcome final.
> Note that the authority declaration is very fine-grained: it is per
> operation and per object. It is therefore much more versatile than the
> wrapper interfaces approach: when using interfaces, a wrapper class must
> decide up-front which interfaces to implement on the object. A MOP can
> decide based on the actual state of the object whether it can perform an
> operation. This can also be handily used to extend any objects with
> language specific meta-properties. Since the API allows any object, not
> just strings, to be used as property identifiers, it is easy to use
> implementation-private objects as property IDs for things like ECMAScript
> "prototype" property and similar. A theoretical ECMAScript MOP would thus
> answer the get and put requests for the prototype property of a POJO, but
> would claim no authority for get and put requests for other properties,
> letting those fall through to the JavaBeans MOP.
One concern I have immediately is that a generic MOP does cause one
unfortunate side effect: it further confounds efforts to inline code. In
stock JRuby 1.0, a MOP would fit well, as almost all method logic is
very generic and has multiple layers of dispatchers, argument
processors, and so on. Moving the dispatch/ivar/property logic out to a
MOP would be easy to do, and would have a minimal impact on functionality.
However, there's one area it would damage JRuby performance
significantly in the 1.0 codebase, and another where it would further
damage it in the 1.1 codebase.
The 1.0 case is our use of Selector Table Indexing (STI). Basically, on
the core Ruby types, a number of methods can be considered "leaves" and
can therefore be called through a more direct path, rather than using
the slow lookup and dispatch logic. In our variation on STI, at startup
all core classes generate a Dispatcher class that uses a table of switch
values to invoke those methods directly. We pass to the call a method
identifier, which is used to look up a switch value, which ultimately
feeds into a large switch that does the actual dispatch. The result of
this code is that for most core methods, invocation follows this path:
caller -> dispatcher -> method
...rather than the usual slow path
caller -> object -> metaclass -> method wrapper -> method
And performance is substantially improved with STI. This mechanism could
fit into a MOP, but it would be complicated to do efficiently given the
large body of method names (IDs) in the Java ecosystem where in JRuby,
it's limited to only core methods.
The 1.1 case is related to my further enhancements to the compiler's
output to improve its optimizability. Specifically, the compiler in 1.0
worked mostly like this:
caller -> object -> metaclass -> method wrapper -> method
...in JRuby trunk it's more like this:
caller -> call site adapter -> method wrapper -> method
With the initial hit to the call site adapter caching a method wrapper
(and STI tables, but that's a separate issue). Bottom line here is that
in order to make it possible to inline Ruby-calling-Ruby, the code from
my compiler will in many (most?) cases skip the metaclass completely.
The performance boost is substantial.
In both cases, we're largely talking about the metaclass and its dynamic
dispatch being initially omitted or eventually omitted for specific
types of calls; and in both cases, the trend is toward *more* of these
sorts of optimizations, not less.
How do we reconcile things like this with the MOP? The MOP will not be a
good solution if it means performance impact for implementations like
JRuby that have optimized fast-path dispatch mechanisms today.
Note that this does not mean the MOP wouldn't be useful for JRuby for
Java types, since those currently have multiple layers of abstraction we
could eliminate with a MOP.
> Developer resources
> -------------------
>
> The project is hosted at SourceForge, under
> <http://www.sf.net/projects/dynalang>. You can use SVN to check out the
> code from there.
>
> I also own the dynalang.org domain, although no content is hosted there
> yet.
>
> If you check out the latest version from SVN, try running ant test. This
> produces both a JUnit test report at build/test/report/index.html and test
> coverage report (generated using EMMA) at
> build/coverage/report/index.html. To build the JavaDoc, use ant javadoc,
> and then open build/javadoc/index.html.
>
> All code is Apache 2.0 licensed.
It seems that your work, the existing Groovy MOP, the Ng MOP by John
Wilson, and all the existing and theoretical dispatch and MOP work we're
doing in JRuby are largely working toward the same goals. What would it
take to get all of us working on this in the same place, under the JLR
project?
I would commit to eventually migrating JRuby to this common MOP assuming
it's feasible and we can all work together to improve it.
- Charlie
the last version is what I plan for Groovy too.
> In both cases, we're largely talking about the metaclass and its dynamic
> dispatch being initially omitted or eventually omitted for specific
> types of calls; and in both cases, the trend is toward *more* of these
> sorts of optimizations, not less.
I think the MetaClass itself must tell if the method it returns can be
cached or not... or another model is to let the MetaClass tell if it
needs to do each dispatch or only the ones really needed. For example
giving the Metaclass a version information, caching the Metaclass and
then checking if the MetaClass was modified would be easy... of course
this assumes the reference to a MetaClass is nether going to change and
that a MetaClass is mutable. Both have big impacts on the design. And
for example in Groovy I need to put bottom on head to accomplish this.
(Groovy will get modifiable but not replaceable MetaClasses, where it
now has replaceable MetaClasses)
bye blackdrag
--
Jochen "blackdrag" Theodorou
Groovy Tech Lead (http://groovy.codehaus.org)
http://blackdragsview.blogspot.com/
What is your mechanism for flushing call sites? In our case, each call
site would register with a global cache manager. Subsequent updates to
method tables would trigger flushing all locations that have cached
affected methods. Currently the call site is monomorphic, and will stop
attempting to cache after a single miss. A polymorphic cache would be
smarter, but also add in conditional logic to choose the correct cached
method. Choices, choices.
>> In both cases, we're largely talking about the metaclass and its dynamic
>> dispatch being initially omitted or eventually omitted for specific
>> types of calls; and in both cases, the trend is toward *more* of these
>> sorts of optimizations, not less.
>
> I think the MetaClass itself must tell if the method it returns can be
> cached or not... or another model is to let the MetaClass tell if it
> needs to do each dispatch or only the ones really needed. For example
> giving the Metaclass a version information, caching the Metaclass and
> then checking if the MetaClass was modified would be easy... of course
> this assumes the reference to a MetaClass is nether going to change and
> that a MetaClass is mutable. Both have big impacts on the design. And
> for example in Groovy I need to put bottom on head to accomplish this.
> (Groovy will get modifiable but not replaceable MetaClasses, where it
> now has replaceable MetaClasses)
I can't think of cases where the method it returns should not be cached.
Can you iterate some for me? Assuming that call sites can always be
validated or flushed when circumstances change, I wouldn't see a reason
to ever *not* cache.
Again, here it sounds like Groovy is moving toward JRuby while JRuby is
moving toward Groovy. We currently have exclusively mutable metaclasses,
because Ruby demands it. But our metaclasses are largely just
name->method pairs, and code external to the metaclass handles the
dispatch logic. But we're looking at the possibility of a more MOP-like
dispatch sequence, where the metaclass would be entirely responsible for
doing the dispatch as well as managing the method tables.
I've been thinking about the optimization aspect of the MOP a bit more
as well. The question of generic code versus specific code could be
rectified in our case by having a generated MOP metaclass that handles
dispatch logic alongside the Ruby metaclass which acts as a Ruby object
in Rubyspace. So all objects would look like:
object
- object state
- Ruby MetaClass
- Method table
- MetaClass object state
- MOP MetaClass
And we would generate MOP metaclasses at runtime as the method table is
updated; so as classes are updated at runtime (as they are *always*
handled in Ruby) new MOP metaclasses would be generated for them that
are always optimal for dispatching.
I do think the MOP must return some sort of "callable" object though;
not doing so eliminates any chance to do call site optimizations. To
rectify the STI-based dispatch in JRuby, I think having the MOP support
both straight-through invocation and callable-based invocation would
work, and this is largely how I've implemented the call site adapters in
JRuby at present:
1. check if we've cached; if so go to 5
2. retrieve STI table from metaclass
3. if method can be STI dispatched, cache STI table and go to 5
4. else retrieve method object from metaclass
5. use cached STI table or method object to invoke method
Now with an extensible MOP metaclass that only supports straight-through
calls, I could probably wire up STI reasonably well. But call-site
optimizations absolutely depend on our ability to get a specific
callable method object out of the metaclass and invoke it directly from
the site.
- Charlie
I think there is a paper about exactly this kind of things... I guess
John gave me this paper: http://citeseer.ist.psu.edu/hlzle91optimizing.html
As for JRuby... do you even have to handle overloaded methods when
calling Java? Or is the type part of the method name? Because in the
later case you would not have to have a different method call mechanism
than you have now.
And I am not so sure about the global thing.. I think the call site
cache might be static per class, maybe even per Thread or per
instance... I am not sure yet. The point is, if there is nothing global,
then there is nothing to flush.
>>> In both cases, we're largely talking about the metaclass and its dynamic
>>> dispatch being initially omitted or eventually omitted for specific
>>> types of calls; and in both cases, the trend is toward *more* of these
>>> sorts of optimizations, not less.
>> I think the MetaClass itself must tell if the method it returns can be
>> cached or not... or another model is to let the MetaClass tell if it
>> needs to do each dispatch or only the ones really needed. For example
>> giving the Metaclass a version information, caching the Metaclass and
>> then checking if the MetaClass was modified would be easy... of course
>> this assumes the reference to a MetaClass is nether going to change and
>> that a MetaClass is mutable. Both have big impacts on the design. And
>> for example in Groovy I need to put bottom on head to accomplish this.
>> (Groovy will get modifiable but not replaceable MetaClasses, where it
>> now has replaceable MetaClasses)
>
> I can't think of cases where the method it returns should not be cached.
depends on the things you do with a customized MetaClass... as for today
Groovy, there are categories and list expansions. A category is a thread
local and time limited addition of methods, thus revisiting the call
site might mean a cached method might have become invalid. On the other
hand a category can "shadow" a normal method, so upon revisiting the
call site we might get an invalid method in cache again. As for List
expansions.. in Groovy it is possible to call a method with a list if
the method takes no list and the number of parameters to the methods is
equal to the size of the list. for example
def m(a,b,c) {
a+b+c
}
assert m([1,2,3]) == m(1,2,3)
the problem is that with caching we have only the list type to test, and
unless we make the list size and possibly the types stored in the list
part of the test we can not ensure that the call will do it right... so
current Groovy does not cache this kind of method call, and I guess in a
new MOP we would not do that too.
Of course I can still cache category methods, but I it requires testing
of the final implementation to say that the caching and added complexity
is worth it.
[...]
> Again, here it sounds like Groovy is moving toward JRuby while JRuby is
> moving toward Groovy.
we meet in the middle ;)
> We currently have exclusively mutable metaclasses,
> because Ruby demands it. But our metaclasses are largely just
> name->method pairs, and code external to the metaclass handles the
> dispatch logic. But we're looking at the possibility of a more MOP-like
> dispatch sequence, where the metaclass would be entirely responsible for
> doing the dispatch as well as managing the method tables.
Well the new logic will be, that the MetaClass returns a method wrapper.
If it wraps custom code or a real method is of no concern. The point is
just not to have to do a long dispatch for every call and probably not
even ask the MetaClass if possible. If possible or not, must the method
wrapper returned by the MetaClass tell.
For example if I want to intercept some method calls, then I do not
return the regular method wrapper I return something custom containing
my code. If I want for example a MetaClass logging each and every method
call, then I can again return a custom Method wrapper that calls first
my logging logic and then it calls the normal original wrapper. Both
cases do allow caching. But if I for example want to call m1 on first
invocation of m and m2 on all invocation following, then I am not
allowed to cache m1, but m2 I can cache.
So MetaClass is still responsible for a dispatch in this model, just the
actual call is done from somewhere else.
so far the theory, but in reality I see some problems... If I return a
wrapper then how do I call it? I am talking about the need to create
arrays and box primitives, that might not need to be boxed. So I guess I
will try to add a number of interfaces allowing different method
signatures, some common cases. the bytecode will then cast to that
interface and make the call using this interface. Most possibly the
callable wrapper will implement more than just on of these interface to
allow more customization... and most probably all of them will implement
a generic version using an Object array.
but creating a new class per method sounds very silly and expensive. So
we might go with a Reflector as we do now. A class that consists mostly
of a table to call methods, just maybe a bit more improved.... such a
class could implement more than just one interface, or have one
reflector for each kind of call... keeping the total amount of generated
classes lower. I am still collecting ideas here.
> I've been thinking about the optimization aspect of the MOP a bit more
> as well. The question of generic code versus specific code could be
> rectified in our case by having a generated MOP metaclass that handles
> dispatch logic alongside the Ruby metaclass which acts as a Ruby object
> in Rubyspace. So all objects would look like:
>
> object
> - object state
> - Ruby MetaClass
> - Method table
> - MetaClass object state
> - MOP MetaClass
>
> And we would generate MOP metaclasses at runtime as the method table is
> updated; so as classes are updated at runtime (as they are *always*
> handled in Ruby) new MOP metaclasses would be generated for them that
> are always optimal for dispatching.
"always optimal for dispatching".... in what way?
> I do think the MOP must return some sort of "callable" object though;
> not doing so eliminates any chance to do call site optimizations.
exactly my thought
> To
> rectify the STI-based dispatch in JRuby, I think having the MOP support
> both straight-through invocation and callable-based invocation would
> work, and this is largely how I've implemented the call site adapters in
> JRuby at present:
>
> 1. check if we've cached; if so go to 5
> 2. retrieve STI table from metaclass
> 3. if method can be STI dispatched, cache STI table and go to 5
> 4. else retrieve method object from metaclass
> 5. use cached STI table or method object to invoke method
>
> Now with an extensible MOP metaclass that only supports straight-through
> calls, I could probably wire up STI reasonably well. But call-site
> optimizations absolutely depend on our ability to get a specific
> callable method object out of the metaclass and invoke it directly from
> the site.
I agree 100%... but is it really a problem?
Yes, the Self PIC paper; I've skimmed it, intending to come back and do
a more thorough read. But it still would require flushing if the
appropriate method for a given metaclass could change at runtime.
> As for JRuby... do you even have to handle overloaded methods when
> calling Java? Or is the type part of the method name? Because in the
> later case you would not have to have a different method call mechanism
> than you have now.
Yes, we handle overloaded methods, selected at runtime. But because Java
methods will never change we have hard caches based on incoming arities
and parameter types, and will consistently dispatch to the same
overloaded method given the same number and types of parameters. Our
current heuristic is reasonably solid; I wouldn't claim it's perfect,
but we don't have any major outstanding bugs right now.
This is part of the "overhead" involved in dispatching to Java code, and
it is not present when calling Ruby methods (since Ruby does not support
overloading). It is also one of the major reasons why dispatching to
Java code from Ruby is slower than calling Ruby code. However
occasionally Ruby methods have differing behavior for differing input
types and will manually branch in code, so for some cases the complexity
and performance is similar.
I believe Groovy supports overloaded signatures even in Groovy code,
yes? So for all method dispatch you must search based on both name and
incoming types?
> And I am not so sure about the global thing.. I think the call site
> cache might be static per class, maybe even per Thread or per
> instance... I am not sure yet. The point is, if there is nothing global,
> then there is nothing to flush.
Not true. Even the most specific, localized cache will need to be
flushed if you have mutable metaclasses, since at any time the list of
methods could change. I believe Groovy supports fully-mutable
metaclasses now with Expando, right?
> depends on the things you do with a customized MetaClass... as for today
> Groovy, there are categories and list expansions. A category is a thread
> local and time limited addition of methods, thus revisiting the call
> site might mean a cached method might have become invalid. On the other
> hand a category can "shadow" a normal method, so upon revisiting the
> call site we might get an invalid method in cache again.
Right, time-spanned thread-local changes to metaclasses alleviate some
of the problems, but as for a general solution this use case is too
restrictive. Ideally a generic MOP would want to assume that all methods
can always be overridden, and optimize for that case. Call site
optimizations should do the same. If we optimize for the worst case we
should be able to get very good performance across languages with the
same code; and then through Atila's MOP's extensibility features we
would ideally be able to include optimizations on a per-language basis,
such as time-spanned thread-local metaclass mutation.
> As for List
> expansions.. in Groovy it is possible to call a method with a list if
> the method takes no list and the number of parameters to the methods is
> equal to the size of the list. for example
>
> def m(a,b,c) {
> a+b+c
> }
> assert m([1,2,3]) == m(1,2,3)
>
> the problem is that with caching we have only the list type to test, and
> unless we make the list size and possibly the types stored in the list
> part of the test we can not ensure that the call will do it right... so
> current Groovy does not cache this kind of method call, and I guess in a
> new MOP we would not do that too.
In JRuby we have this issue as well, since Ruby allows expanding lists
to arguments. We resolve this by having a two-phase method selector;
first it gets the method object appropriate for the name sent, then it
chooses the method appropriate for the arguments given. So at the call
site you'd cache only the first phase, which would provide a mechanism
for selecting the second phase call target (and presumably doing its own
caching for specific combinations of arity and type).
> Of course I can still cache category methods, but I it requires testing
> of the final implementation to say that the caching and added complexity
> is worth it.
The caching will only be worth it if it brings sufficient specificity to
the call path. My initial experiments with call site caching in the
interpreter we practically useless; the call site was 100% generic and
all calls went through the same code. The current call site optimization
is however very effective for compiled Ruby code, since it introduces a
small amount of genericity into a largely specific call path. I am not
familiar enough with the level of complexity in Groovy's call path to
know if call site optimizations will be worth it or not.
>> Again, here it sounds like Groovy is moving toward JRuby while JRuby is
>> moving toward Groovy.
>
> we meet in the middle ;)
Finally!
>> We currently have exclusively mutable metaclasses,
>> because Ruby demands it. But our metaclasses are largely just
>> name->method pairs, and code external to the metaclass handles the
>> dispatch logic. But we're looking at the possibility of a more MOP-like
>> dispatch sequence, where the metaclass would be entirely responsible for
>> doing the dispatch as well as managing the method tables.
>
> Well the new logic will be, that the MetaClass returns a method wrapper.
> If it wraps custom code or a real method is of no concern. The point is
> just not to have to do a long dispatch for every call and probably not
> even ask the MetaClass if possible. If possible or not, must the method
> wrapper returned by the MetaClass tell.
Excellent, this is a good path to follow. This is how JRuby has always
done dispatch, and it's proven much easier to optimize than always
calling through the metaclass.
We really must cooperate; everything I've heard about your plans for the
new Groovy MOP and the Ng MOP sound almost identical to JRuby's current
dispatch logic and future plans. Atila's MOP seed project might be a
good place for us to combine efforts.
> For example if I want to intercept some method calls, then I do not
> return the regular method wrapper I return something custom containing
> my code. If I want for example a MetaClass logging each and every method
> call, then I can again return a custom Method wrapper that calls first
> my logging logic and then it calls the normal original wrapper. Both
> cases do allow caching. But if I for example want to call m1 on first
> invocation of m and m2 on all invocation following, then I am not
> allowed to cache m1, but m2 I can cache.
This level of detail seems a bit extraneous. I would question the
utility of building this cache complexity into the MOP directly. Why not
just have the first method (m) get cached and thrown away? Or explicitly
have the call to m flush the cache?
I'm still not sure I see how uncacheable methods are useful if the call
sites and language runtime are smart enough to flush them or not cache
them in the first place. It does not seem like something I would want to
build into the MOP.
Generally, I don't think call-site caching semantics should even enter
into the MOP implementation; the MOP should just return a callable
object given a set of inputs, and the consumer of that callable should
be responsible for caching it. The only limited place I could see
caching entering into the MOP would be an event hook that the consumer
could register to know when to flush caches. It seems to me that if the
metaclass is smart enough to know a given method shouldn't be cached, it
would be simpler for it to just tell the caller to flush. Adding caching
logic into the MOP introduces the additional complexity of caching
where--in many cases--caching won't even happen.
> So MetaClass is still responsible for a dispatch in this model, just the
> actual call is done from somewhere else.
>
> so far the theory, but in reality I see some problems... If I return a
> wrapper then how do I call it? I am talking about the need to create
> arrays and box primitives, that might not need to be boxed. So I guess I
> will try to add a number of interfaces allowing different method
> signatures, some common cases. the bytecode will then cast to that
> interface and make the call using this interface. Most possibly the
> callable wrapper will implement more than just on of these interface to
> allow more customization... and most probably all of them will implement
> a generic version using an Object array.
Currently JRuby has a generic IRubyObject[] requirement for all
arguments passed through the call path. This does introduce, as you
would suspect, the overhead of wrapping all arguments in IRubyObject[].
We would like to eliminate this overhead completely, and to that end the
call site adapter (CallAdapter) provides a number of "call" signatures
for arities 0, 1, 2, 3, n and with or without a block (closure)
parameter. This is as far as the arity-specific signatures go, but it's
a start; we eventually hope to have the full call path use as specific
arities as possible.
But to your question...how does Groovy do this now? I don't know what
sort of overhead we've seen from always using IRubyObject[]. It doesn't
seem to be hindering performance enough to show up on profiling, but I
don't know for sure. It's certainly simpler to just pass IRubyObject[]
all the time though :)
> but creating a new class per method sounds very silly and expensive. So
> we might go with a Reflector as we do now. A class that consists mostly
> of a table to call methods, just maybe a bit more improved.... such a
> class could implement more than just one interface, or have one
> reflector for each kind of call... keeping the total amount of generated
> classes lower. I am still collecting ideas here.
In both AOT and JIT mode, JRuby will generate a class per method, and we
have not seen that it causes any severe impact to memory or performance
(yet). Given that in a typical app run about half the called Ruby
methods are currently getting compiled, it seems like it's not such a
bad way to go. The overhead of having a class per method (and in our
case, a *classloader* per method) is pretty gross, but it's manageable.
Note also that JRuby binds all Java-based methods into Ruby using
generated classes as well. We've got scads of generated classes (and
their parent classloaders) floating around as a result, and nary a
memory or permgen complaint so far.
Your reflector is roughly equivalent to our dispatcher, which uses a
table-based dispatch. But we only use that for a limited set of core
JRuby methods we know we can call directly; for most Ruby methods
additional per-call logic is required that doesn't fit well into a
table-based dispatch. Eventually I'd love to see all dispatch be
table-based, but it seems we can get pretty solid performance just from
a bit of call site logic.
>> And we would generate MOP metaclasses at runtime as the method table is
>> updated; so as classes are updated at runtime (as they are *always*
>> handled in Ruby) new MOP metaclasses would be generated for them that
>> are always optimal for dispatching.
>
> "always optimal for dispatching".... in what way?
i.e. using table-based dispatch rather than an expensive hash or
multimethod lookup, using a single piece of specific code to locate
callable methods rather than a generic piece of code that queries
runtime structures; and so on.
>> I do think the MOP must return some sort of "callable" object though;
>> not doing so eliminates any chance to do call site optimizations.
>
> exactly my thought
>
>> To
>> rectify the STI-based dispatch in JRuby, I think having the MOP support
>> both straight-through invocation and callable-based invocation would
>> work, and this is largely how I've implemented the call site adapters in
>> JRuby at present:
>>
>> 1. check if we've cached; if so go to 5
>> 2. retrieve STI table from metaclass
>> 3. if method can be STI dispatched, cache STI table and go to 5
>> 4. else retrieve method object from metaclass
>> 5. use cached STI table or method object to invoke method
>>
>> Now with an extensible MOP metaclass that only supports straight-through
>> calls, I could probably wire up STI reasonably well. But call-site
>> optimizations absolutely depend on our ability to get a specific
>> callable method object out of the metaclass and invoke it directly from
>> the site.
>
> I agree 100%... but is it really a problem?
It's a problem if others don't agree and we have a general-purpose MOP
that only supports straight-through calling...but I'd wager we can come
to a consensus on this one.
What's your timeframe for your new MOP? Is there a potential it could be
developed in a reusable way, or that it could be developed on top of
Atila's work (which admittedly I have not looked at yet).
I guess the question I'm asking is "if work started in earnest on a
common MOP framework, could Groovy potentially migrate toward it?" For
JRuby, the answer is a resounding "yes". I see no benefit to duplicating
efforts.
- Charlie
I think the PIC gets replaced by a new one.. I am still thinking about
how to make a efficient one without generating new classes all the time.
Maybe something like... using an array of these wrappers, each selected
by testing the parameter and receiver classes? Storing the classes in
another array? I am a bit concerned that this is too slow.
>> As for JRuby... do you even have to handle overloaded methods when
>> calling Java? Or is the type part of the method name? Because in the
>> later case you would not have to have a different method call mechanism
>> than you have now.
>
> Yes, we handle overloaded methods, selected at runtime. But because Java
> methods will never change we have hard caches based on incoming arities
> and parameter types, and will consistently dispatch to the same
> overloaded method given the same number and types of parameters. Our
> current heuristic is reasonably solid; I wouldn't claim it's perfect,
> but we don't have any major outstanding bugs right now.
ah ok... interesting idea. But I take it, that you can't replace a java
method with a ruby method that is at last seen by the ruby universe.
That is different to Groovy, where we can add methods to java classes,
even replacing them from the point of view Groovy has. Of course they
are not really replaced.
> This is part of the "overhead" involved in dispatching to Java code, and
> it is not present when calling Ruby methods (since Ruby does not support
> overloading). It is also one of the major reasons why dispatching to
> Java code from Ruby is slower than calling Ruby code. However
> occasionally Ruby methods have differing behavior for differing input
> types and will manually branch in code, so for some cases the complexity
> and performance is similar.
why does behave ruby different for different input types in some cases?
> I believe Groovy supports overloaded signatures even in Groovy code,
> yes?
yes. Since we wanted to do no name mangling and since we wanted to allow
implementing a class in Groovy, we needed that.
> So for all method dispatch you must search based on both name and
> incoming types?
yes. But how much work that is, is based on the call site. The call site
could still be using the same types all the time, and then a PIC is very
effective, it just have to test some types for equality, that's all.
>> And I am not so sure about the global thing.. I think the call site
>> cache might be static per class, maybe even per Thread or per
>> instance... I am not sure yet. The point is, if there is nothing global,
>> then there is nothing to flush.
>
> Not true. Even the most specific, localized cache will need to be
> flushed if you have mutable metaclasses, since at any time the list of
> methods could change. I believe Groovy supports fully-mutable
> metaclasses now with Expando, right?
right. That's why the MetaClasses are going to be"versioned", and if the
version of the receiver changes the PIC needs possibly recreation... aka
flushing. In the current design the MetaClass itself is doing the
caching, so the Metaclass can have full influence on that part.... but
that's going to change then... a call site might need to handle more
than one receiver type.
>> depends on the things you do with a customized MetaClass... as for today
>> Groovy, there are categories and list expansions. A category is a thread
>> local and time limited addition of methods, thus revisiting the call
>> site might mean a cached method might have become invalid. On the other
>> hand a category can "shadow" a normal method, so upon revisiting the
>> call site we might get an invalid method in cache again.
>
> Right, time-spanned thread-local changes to metaclasses alleviate some
> of the problems, but as for a general solution this use case is too
> restrictive. Ideally a generic MOP would want to assume that all methods
> can always be overridden, and optimize for that case.
doesn't sound like something you can optimize much for ;)
> Call site
> optimizations should do the same. If we optimize for the worst case we
> should be able to get very good performance across languages with the
> same code; and then through Atila's MOP's extensibility features we
> would ideally be able to include optimizations on a per-language basis,
> such as time-spanned thread-local metaclass mutation.
sure, but depends on how you define the MOP.
[...]
> In JRuby we have this issue as well, since Ruby allows expanding lists
> to arguments. We resolve this by having a two-phase method selector;
> first it gets the method object appropriate for the name sent, then it
> chooses the method appropriate for the arguments given. So at the call
> site you'd cache only the first phase, which would provide a mechanism
> for selecting the second phase call target (and presumably doing its own
> caching for specific combinations of arity and type).
the time consuming part in Groovy is getting the actual method to call
form a list. And this part remains even if I cache the first phase. In
the current architecture caching this phase makes not much sense for Groovy.
>> Of course I can still cache category methods, but I it requires testing
>> of the final implementation to say that the caching and added complexity
>> is worth it.
>
> The caching will only be worth it if it brings sufficient specificity to
> the call path. My initial experiments with call site caching in the
> interpreter we practically useless; the call site was 100% generic and
> all calls went through the same code. The current call site optimization
> is however very effective for compiled Ruby code, since it introduces a
> small amount of genericity into a largely specific call path. I am not
> familiar enough with the level of complexity in Groovy's call path to
> know if call site optimizations will be worth it or not.
I am sure they are.
[...]
>>> We currently have exclusively mutable metaclasses,
>>> because Ruby demands it. But our metaclasses are largely just
>>> name->method pairs, and code external to the metaclass handles the
>>> dispatch logic. But we're looking at the possibility of a more MOP-like
>>> dispatch sequence, where the metaclass would be entirely responsible for
>>> doing the dispatch as well as managing the method tables.
>> Well the new logic will be, that the MetaClass returns a method wrapper.
>> If it wraps custom code or a real method is of no concern. The point is
>> just not to have to do a long dispatch for every call and probably not
>> even ask the MetaClass if possible. If possible or not, must the method
>> wrapper returned by the MetaClass tell.
>
> Excellent, this is a good path to follow. This is how JRuby has always
> done dispatch, and it's proven much easier to optimize than always
> calling through the metaclass.
I hope so ;)
> We really must cooperate; everything I've heard about your plans for the
> new Groovy MOP and the Ng MOP sound almost identical to JRuby's current
> dispatch logic and future plans. Atila's MOP seed project might be a
> good place for us to combine efforts.
maybe, yes.. on the other hand I think the solution is kind of obvious
and maybe there is a even better one than we thought... but
invokedynamic seems to go the same route, so a migration should be very
easy later... that makes the ideas extremely attractive. And maybe it
isn't that wrong then.
>> For example if I want to intercept some method calls, then I do not
>> return the regular method wrapper I return something custom containing
>> my code. If I want for example a MetaClass logging each and every method
>> call, then I can again return a custom Method wrapper that calls first
>> my logging logic and then it calls the normal original wrapper. Both
>> cases do allow caching. But if I for example want to call m1 on first
>> invocation of m and m2 on all invocation following, then I am not
>> allowed to cache m1, but m2 I can cache.
>
> This level of detail seems a bit extraneous. I would question the
> utility of building this cache complexity into the MOP directly. Why not
> just have the first method (m) get cached and thrown away? Or explicitly
> have the call to m flush the cache?
it depends on what you will allow the user of the language to do. direct
manipulation of the MetaClass can be fatal, but very sexy. And recent
developments in Groovy are going very much in this direction.
But coming back to your question... why cache m at all? If I know it is
thrown away later, then I wouldn't bother with caching it. Having to
explicitly flush the site cache would mean you need a reference to it
somewhere... I would prefer an automatic solution here if possible. One
hidden completely from the user.. this allows us to replace it with
other solutions if needed
> I'm still not sure I see how uncacheable methods are useful if the call
> sites and language runtime are smart enough to flush them or not cache
> them in the first place. It does not seem like something I would want to
> build into the MOP.
I guess you misunderstood... the whole point is to have the information
if the provided method wrapper should be cached or not. This hint can be
used to avoid useless modifications to the PIC. That's all. Being able
to mark a method as uncacheable is thought to be helping to build a more
intelligent cache.
> Generally, I don't think call-site caching semantics should even enter
> into the MOP implementation; the MOP should just return a callable
> object given a set of inputs, and the consumer of that callable should
> be responsible for caching it.
I wrote that I thought ;)
> The only limited place I could see
> caching entering into the MOP would be an event hook that the consumer
> could register to know when to flush caches. It seems to me that if the
> metaclass is smart enough to know a given method shouldn't be cached, it
> would be simpler for it to just tell the caller to flush. Adding caching
> logic into the MOP introduces the additional complexity of caching
> where--in many cases--caching won't even happen.
I see that more as a hint of "you might cache me" not you have to.
>> So MetaClass is still responsible for a dispatch in this model, just the
>> actual call is done from somewhere else.
>>
>> so far the theory, but in reality I see some problems... If I return a
>> wrapper then how do I call it? I am talking about the need to create
>> arrays and box primitives, that might not need to be boxed. So I guess I
>> will try to add a number of interfaces allowing different method
>> signatures, some common cases. the bytecode will then cast to that
>> interface and make the call using this interface. Most possibly the
>> callable wrapper will implement more than just on of these interface to
>> allow more customization... and most probably all of them will implement
>> a generic version using an Object array.
>
> Currently JRuby has a generic IRubyObject[] requirement for all
> arguments passed through the call path. This does introduce, as you
> would suspect, the overhead of wrapping all arguments in IRubyObject[].
> We would like to eliminate this overhead completely, and to that end the
> call site adapter (CallAdapter) provides a number of "call" signatures
> for arities 0, 1, 2, 3, n and with or without a block (closure)
> parameter. This is as far as the arity-specific signatures go, but it's
> a start; we eventually hope to have the full call path use as specific
> arities as possible.
would be a Object[] in case of Groovy... I was thinking about the
primitive types here. but maybe it is time to use wrappers for them as
well. Modifiable wrappers to primitive types can be very efficient...
> But to your question...how does Groovy do this now? I don't know what
> sort of overhead we've seen from always using IRubyObject[]. It doesn't
> seem to be hindering performance enough to show up on profiling, but I
> don't know for sure. It's certainly simpler to just pass IRubyObject[]
> all the time though :)
having to create an Object[] for each call from Groovy has a negative
effect, even it is a small one. Storing primitive typed values in their
wrappers of java.lang is another hit on performance. These are of course
small. But they take their time too.
>> but creating a new class per method sounds very silly and expensive. So
>> we might go with a Reflector as we do now. A class that consists mostly
>> of a table to call methods, just maybe a bit more improved.... such a
>> class could implement more than just one interface, or have one
>> reflector for each kind of call... keeping the total amount of generated
>> classes lower. I am still collecting ideas here.
>
> In both AOT and JIT mode, JRuby will generate a class per method, and we
> have not seen that it causes any severe impact to memory or performance
> (yet). Given that in a typical app run about half the called Ruby
> methods are currently getting compiled, it seems like it's not such a
> bad way to go. The overhead of having a class per method (and in our
> case, a *classloader* per method) is pretty gross, but it's manageable.
ah.. good to know... I was wondering about that...
> Note also that JRuby binds all Java-based methods into Ruby using
> generated classes as well. We've got scads of generated classes (and
> their parent classloaders) floating around as a result, and nary a
> memory or permgen complaint so far.
but we got them for permgen... but it might have been related to bugs
that are fixed now.
> Your reflector is roughly equivalent to our dispatcher, which uses a
> table-based dispatch. But we only use that for a limited set of core
> JRuby methods we know we can call directly; for most Ruby methods
> additional per-call logic is required that doesn't fit well into a
> table-based dispatch. Eventually I'd love to see all dispatch be
> table-based, but it seems we can get pretty solid performance just from
> a bit of call site logic.
The reflector is just there to make the selected call. The Metaclass
does still have to select the method to be called. And then it is really
just a jump table.
[...]
>>> Now with an extensible MOP metaclass that only supports straight-through
>>> calls, I could probably wire up STI reasonably well. But call-site
>>> optimizations absolutely depend on our ability to get a specific
>>> callable method object out of the metaclass and invoke it directly from
>>> the site.
>> I agree 100%... but is it really a problem?
>
> It's a problem if others don't agree and we have a general-purpose MOP
> that only supports straight-through calling...but I'd wager we can come
> to a consensus on this one.
hehe
> What's your timeframe for your new MOP? Is there a potential it could be
> developed in a reusable way, or that it could be developed on top of
> Atila's work (which admittedly I have not looked at yet).
I have not looked at that too... yet. I am doing experiments here and
there for the new MetaClass system, but it is planed to be used after
1.1 final is out, which should be the next months... I planed to go step
by step then... completly throwing away the old MetaClass in the first
step, optimizing this as much as it seems reasonable and then start to
reapply the old structure while trying to keep the performance... This
will take a while. Much time.
> I guess the question I'm asking is "if work started in earnest on a
> common MOP framework, could Groovy potentially migrate toward it?" For
> JRuby, the answer is a resounding "yes". I see no benefit to duplicating
> efforts.
it depends on the MOP. I think many languages have no use for mutable
MetaClasses, or even exchangeable ones. But this has a major impact on
the logic. Another thing is overloaded methods.... not every language
allows them and doing a dispatch based on receiver/name is much more
easy to do. Ho many languages besides Groovy would get something out of
a MetaClass like this? On the other hand the ability to at last mutate a
MetaClass proofed to be an interesting application for Grails and
others. So at last a replacement for this must be given... but I guess
there is no real replacement for exchanging a MetaClass with a custom
version. So I can't answer if Groovy would migrate to that. It depends
on the outcome.
I used the two-array approach with a small static size (say 2 or 3). But
that was when inline caching showed no gains (i.e. before the compiler),
so I can't say how it would help the current compiler. It could be
amazing or it could be dog slow.
>> Yes, we handle overloaded methods, selected at runtime. But because Java
>> methods will never change we have hard caches based on incoming arities
>> and parameter types, and will consistently dispatch to the same
>> overloaded method given the same number and types of parameters. Our
>> current heuristic is reasonably solid; I wouldn't claim it's perfect,
>> but we don't have any major outstanding bugs right now.
>
> ah ok... interesting idea. But I take it, that you can't replace a java
> method with a ruby method that is at last seen by the ruby universe.
> That is different to Groovy, where we can add methods to java classes,
> even replacing them from the point of view Groovy has. Of course they
> are not really replaced.
We can indeed. All Java classes in Ruby space are given Ruby
metaclasses, with all capabilities that bestows. You can even add
singleton methods to a specific object instance, and all Ruby will see
it (through the painful use of weak object-to-wrapper caches we're
trying to find ways to eliminate).
Java classes generally look and feel just like Ruby classes within Ruby
space, even down to nasty features like singletons.
>> This is part of the "overhead" involved in dispatching to Java code, and
>> it is not present when calling Ruby methods (since Ruby does not support
>> overloading). It is also one of the major reasons why dispatching to
>> Java code from Ruby is slower than calling Ruby code. However
>> occasionally Ruby methods have differing behavior for differing input
>> types and will manually branch in code, so for some cases the complexity
>> and performance is similar.
>
> why does behave ruby different for different input types in some cases?
For the same reasons you might have different signatures in Java. Take
String#[] for example...it can accept another String, a Fixnum index, a
Regexp...etc. Each has different behavior, so the impl of []
conditionally chooses the appropriate logic based on incoming types.
>> I believe Groovy supports overloaded signatures even in Groovy code,
>> yes?
>
> yes. Since we wanted to do no name mangling and since we wanted to allow
> implementing a class in Groovy, we needed that.
When a Java class is extended or an interface is implemented in Ruby
code, all dispatches for a specific name go to the same piece of Ruby
code. That means you'd have to manually switch on the different types of
input, but I haven't heard anyone complain about that much. Usually
interfaces don't have so many overloaded methods that they're a problem
to implement in Ruby.
>> So for all method dispatch you must search based on both name and
>> incoming types?
>
> yes. But how much work that is, is based on the call site. The call site
> could still be using the same types all the time, and then a PIC is very
> effective, it just have to test some types for equality, that's all.
It's still a lot more work than Java, of course, since you can't
determine it at compile-time. In JRuby, we basically build up a hash
based on the types of arguments, and use that hash to lookup the
appropriate Java method to call. It's fast enough that overloaded method
selection is no longer the primary bottleneck in calling Java code.
But you're right, having that logic at the call site would be a great
boon, and we are working toward that end as well; at first with specific
arities, and later with specific types. It's complicated stuff though,
especially when we're talking about raw bytecode in most cases.
>> Not true. Even the most specific, localized cache will need to be
>> flushed if you have mutable metaclasses, since at any time the list of
>> methods could change. I believe Groovy supports fully-mutable
>> metaclasses now with Expando, right?
>
> right. That's why the MetaClasses are going to be"versioned", and if the
> version of the receiver changes the PIC needs possibly recreation... aka
> flushing. In the current design the MetaClass itself is doing the
> caching, so the Metaclass can have full influence on that part.... but
> that's going to change then... a call site might need to handle more
> than one receiver type.
Versioning works, but it adds in the logic of flushing the cache to
every single call, where a push-based flushing mechanism only adds
overhead to method table manipulation. The more logic we can keep out of
the call sites the better, and constantly version-checking is at the top
of my list of things to avoid.
>> Right, time-spanned thread-local changes to metaclasses alleviate some
>> of the problems, but as for a general solution this use case is too
>> restrictive. Ideally a generic MOP would want to assume that all methods
>> can always be overridden, and optimize for that case.
>
> doesn't sound like something you can optimize much for ;)
Absolutely you can optimize for it...after all, HotSpot does :)
>> We really must cooperate; everything I've heard about your plans for the
>> new Groovy MOP and the Ng MOP sound almost identical to JRuby's current
>> dispatch logic and future plans. Atila's MOP seed project might be a
>> good place for us to combine efforts.
>
> maybe, yes.. on the other hand I think the solution is kind of obvious
> and maybe there is a even better one than we thought... but
> invokedynamic seems to go the same route, so a migration should be very
> easy later... that makes the ideas extremely attractive. And maybe it
> isn't that wrong then.
That's my thinking as well. invokedynamic is going to feel very similar
to the dynamic invocation techniques we're talking about here for the
various MOPs, and ideally we want something that will be able to take
advantage of invokedynamic when it comes to life.
>> This level of detail seems a bit extraneous. I would question the
>> utility of building this cache complexity into the MOP directly. Why not
>> just have the first method (m) get cached and thrown away? Or explicitly
>> have the call to m flush the cache?
>
> it depends on what you will allow the user of the language to do. direct
> manipulation of the MetaClass can be fatal, but very sexy. And recent
> developments in Groovy are going very much in this direction.
>
> But coming back to your question... why cache m at all? If I know it is
> thrown away later, then I wouldn't bother with caching it. Having to
> explicitly flush the site cache would mean you need a reference to it
> somewhere... I would prefer an automatic solution here if possible. One
> hidden completely from the user.. this allows us to replace it with
> other solutions if needed
Isn't it hidden from the user anyway? Or is groovy adding some features
that allow you to say "immediately after this method is called, replace
it with another implementation"? I've never heard of such a feature, but
I suppose it would have its uses.
And yes, you'd have a reference to every call site, but every call site
could just call straight through to the cached code without doing
consistency checks. If consistency checking all the time can be made as
fast, I'd love to hear how.
>> I'm still not sure I see how uncacheable methods are useful if the call
>> sites and language runtime are smart enough to flush them or not cache
>> them in the first place. It does not seem like something I would want to
>> build into the MOP.
>
> I guess you misunderstood... the whole point is to have the information
> if the provided method wrapper should be cached or not. This hint can be
> used to avoid useless modifications to the PIC. That's all. Being able
> to mark a method as uncacheable is thought to be helping to build a more
> intelligent cache.
So this is more a feature/attribute of the method wrapper than the MOP,
isn't it? The MOP certainly shouldn't have to know about method caching
at call sites...
>> Generally, I don't think call-site caching semantics should even enter
>> into the MOP implementation; the MOP should just return a callable
>> object given a set of inputs, and the consumer of that callable should
>> be responsible for caching it.
>
> I wrote that I thought ;)
Perhaps we just misunderstood each other :) or perhaps my understanding
of what would be included in the MOP is different from yours. Maybe it's
worth defining a MOP's responsibilities a bit more clearly?
>> Currently JRuby has a generic IRubyObject[] requirement for all
>> arguments passed through the call path. This does introduce, as you
>> would suspect, the overhead of wrapping all arguments in IRubyObject[].
>> We would like to eliminate this overhead completely, and to that end the
>> call site adapter (CallAdapter) provides a number of "call" signatures
>> for arities 0, 1, 2, 3, n and with or without a block (closure)
>> parameter. This is as far as the arity-specific signatures go, but it's
>> a start; we eventually hope to have the full call path use as specific
>> arities as possible.
>
> would be a Object[] in case of Groovy... I was thinking about the
> primitive types here. but maybe it is time to use wrappers for them as
> well. Modifiable wrappers to primitive types can be very efficient...
Have you done any measurement to see how much overhead is introduced
constructing all those Object[]?
>> But to your question...how does Groovy do this now? I don't know what
>> sort of overhead we've seen from always using IRubyObject[]. It doesn't
>> seem to be hindering performance enough to show up on profiling, but I
>> don't know for sure. It's certainly simpler to just pass IRubyObject[]
>> all the time though :)
>
> having to create an Object[] for each call from Groovy has a negative
> effect, even it is a small one. Storing primitive typed values in their
> wrappers of java.lang is another hit on performance. These are of course
> small. But they take their time too.
I would hope that many uses of the primitive wrappers can be picked up
by the JVM and optimized away, but perhaps that requires more static
typing and explicit compilation logic?
>> What's your timeframe for your new MOP? Is there a potential it could be
>> developed in a reusable way, or that it could be developed on top of
>> Atila's work (which admittedly I have not looked at yet).
>
> I have not looked at that too... yet. I am doing experiments here and
> there for the new MetaClass system, but it is planed to be used after
> 1.1 final is out, which should be the next months... I planed to go step
> by step then... completly throwing away the old MetaClass in the first
> step, optimizing this as much as it seems reasonable and then start to
> reapply the old structure while trying to keep the performance... This
> will take a while. Much time.
Sounds like an effort that could use help...and potentially would
benefit from a common MOP project.
>> I guess the question I'm asking is "if work started in earnest on a
>> common MOP framework, could Groovy potentially migrate toward it?" For
>> JRuby, the answer is a resounding "yes". I see no benefit to duplicating
>> efforts.
>
> it depends on the MOP. I think many languages have no use for mutable
> MetaClasses, or even exchangeable ones. But this has a major impact on
> the logic. Another thing is overloaded methods.... not every language
> allows them and doing a dispatch based on receiver/name is much more
> easy to do. Ho many languages besides Groovy would get something out of
> a MetaClass like this? On the other hand the ability to at last mutate a
> MetaClass proofed to be an interesting application for Grails and
> others. So at last a replacement for this must be given... but I guess
> there is no real replacement for exchanging a MetaClass with a custom
> version. So I can't answer if Groovy would migrate to that. It depends
> on the outcome.
The outcome depends on us. If we want it to be powerful and useful for
Groovy (or JRuby, or Jython), we need only to snap our fingers...and
then spend a few grueling days or weeks making it happen. But we're all
going to spend that time anyway...it would be beneficial to share the load.
- Charlie
well... another ideas for the new MetaClass system I have is to add a
listener mechanism allowing other parts of the program to listen for
mutations. I thought to use it to notify a child MetaClass about a
method being added, but if the callsite registers a listener, then the
call site could use that information as well. This leaves the logic to
rebuild the PIC to the Thread making the change, which should have
positive influence.
[...]
>>> This level of detail seems a bit extraneous. I would question the
>>> utility of building this cache complexity into the MOP directly. Why not
>>> just have the first method (m) get cached and thrown away? Or explicitly
>>> have the call to m flush the cache?
>> it depends on what you will allow the user of the language to do. direct
>> manipulation of the MetaClass can be fatal, but very sexy. And recent
>> developments in Groovy are going very much in this direction.
>>
>> But coming back to your question... why cache m at all? If I know it is
>> thrown away later, then I wouldn't bother with caching it. Having to
>> explicitly flush the site cache would mean you need a reference to it
>> somewhere... I would prefer an automatic solution here if possible. One
>> hidden completely from the user.. this allows us to replace it with
>> other solutions if needed
>
> Isn't it hidden from the user anyway? Or is groovy adding some features
> that allow you to say "immediately after this method is called, replace
> it with another implementation"? I've never heard of such a feature, but
> I suppose it would have its uses.
in Groovy it is not much hidden. Every GroovyObject has a setter for a
MetaClass for example. We have a call back in the MetaClassRegistry
allowing people to add custom meta classes and we have a loading rule
that allows people to put precompiled MetaClasses... so it is quite
open... and making its problems. The migration of the MOP will be heavy
stuff for the Groovy Users, but I see no future in the current
architecture. It is starting to make more problems than it solves.
> And yes, you'd have a reference to every call site, but every call site
> could just call straight through to the cached code without doing
> consistency checks. If consistency checking all the time can be made as
> fast, I'd love to hear how.
for a PIC you can't remove the checks to the parameters. At last I don't
know how it should still work in a correct way then.
>>> I'm still not sure I see how uncacheable methods are useful if the call
>>> sites and language runtime are smart enough to flush them or not cache
>>> them in the first place. It does not seem like something I would want to
>>> build into the MOP.
>> I guess you misunderstood... the whole point is to have the information
>> if the provided method wrapper should be cached or not. This hint can be
>> used to avoid useless modifications to the PIC. That's all. Being able
>> to mark a method as uncacheable is thought to be helping to build a more
>> intelligent cache.
>
> So this is more a feature/attribute of the method wrapper than the MOP,
> isn't it? The MOP certainly shouldn't have to know about method caching
> at call sites...
yes and no.... If you define the method wrapper to be part of the MOP,
and without the wrapper you can not return something in your MetaClass,
then it is of concern, unless we say the language itself has to take
care of that and add an interface or something to the wrapper by itself.
>>> Generally, I don't think call-site caching semantics should even enter
>>> into the MOP implementation; the MOP should just return a callable
>>> object given a set of inputs, and the consumer of that callable should
>>> be responsible for caching it.
>> I wrote that I thought ;)
>
> Perhaps we just misunderstood each other :) or perhaps my understanding
> of what would be included in the MOP is different from yours. Maybe it's
> worth defining a MOP's responsibilities a bit more clearly?
+1
>>> Currently JRuby has a generic IRubyObject[] requirement for all
>>> arguments passed through the call path. This does introduce, as you
>>> would suspect, the overhead of wrapping all arguments in IRubyObject[].
>>> We would like to eliminate this overhead completely, and to that end the
>>> call site adapter (CallAdapter) provides a number of "call" signatures
>>> for arities 0, 1, 2, 3, n and with or without a block (closure)
>>> parameter. This is as far as the arity-specific signatures go, but it's
>>> a start; we eventually hope to have the full call path use as specific
>>> arities as possible.
>> would be a Object[] in case of Groovy... I was thinking about the
>> primitive types here. but maybe it is time to use wrappers for them as
>> well. Modifiable wrappers to primitive types can be very efficient...
>
> Have you done any measurement to see how much overhead is introduced
> constructing all those Object[]?
well... in real code I can't test it, because I need the array. If I do
a test like:
> static void testArray() {
> Object x = new BigDecimal("0");
> Object y = new BigDecimal("0");
> for (int i=0; i<runs; i++) {
> Object[] a=new Object[2];
> a[0] = x;
> a[1] = y;
> foo1(a);
> }
> }
>
> static void testReference() {
> Object x = new BigDecimal("0");
> Object y = new BigDecimal("0");
> for (int i=0; i<runs; i++) {
> foo2(x,y);
> }
> }
>
> static void foo1(Object[] a) {
> bar(a[0],a[1]);
> }
>
> static void foo2(Object x, Object y) {
> bar(x,y);
> }
>
> static void bar(Object x, Object y) {
> return;
> }
then my test do say of course nearly nothing, because I don't get a time
for foo2/bar. I guess it is inlined. But the other version consumes
time. so even if it does not really mean much, it seems to give the
inlining a problem... or something else for hotspot. Whatever it is, the
time goes not near to 0. That was in server mode... in client mode my
test shows that testReference is about 35 times faster.
>>> But to your question...how does Groovy do this now? I don't know what
>>> sort of overhead we've seen from always using IRubyObject[]. It doesn't
>>> seem to be hindering performance enough to show up on profiling, but I
>>> don't know for sure. It's certainly simpler to just pass IRubyObject[]
>>> all the time though :)
>> having to create an Object[] for each call from Groovy has a negative
>> effect, even it is a small one. Storing primitive typed values in their
>> wrappers of java.lang is another hit on performance. These are of course
>> small. But they take their time too.
>
> I would hope that many uses of the primitive wrappers can be picked up
> by the JVM and optimized away, but perhaps that requires more static
> typing and explicit compilation logic?
I plan to add a primitive value calculation mode which is enabled if the
MetaClass for Integer is unchanged. Then I will try to make the
calculation using primitve types without the MetaClass as much as
possible. This should give a massive speedup for integer calculation
based tests. On the other hand... I know that a wrapper can be very fast
as well if the most commonly used methods are defined on the wrapper and
the runtime makes direct calls to these methods where possible. I have
still to test how much difference in speed this might make.
>>> What's your timeframe for your new MOP? Is there a potential it could be
>>> developed in a reusable way, or that it could be developed on top of
>>> Atila's work (which admittedly I have not looked at yet).
>> I have not looked at that too... yet. I am doing experiments here and
>> there for the new MetaClass system, but it is planed to be used after
>> 1.1 final is out, which should be the next months... I planed to go step
>> by step then... completly throwing away the old MetaClass in the first
>> step, optimizing this as much as it seems reasonable and then start to
>> reapply the old structure while trying to keep the performance... This
>> will take a while. Much time.
>
> Sounds like an effort that could use help...and potentially would
> benefit from a common MOP project.
sure.
>>> I guess the question I'm asking is "if work started in earnest on a
>>> common MOP framework, could Groovy potentially migrate toward it?" For
>>> JRuby, the answer is a resounding "yes". I see no benefit to duplicating
>>> efforts.
>> it depends on the MOP. I think many languages have no use for mutable
>> MetaClasses, or even exchangeable ones. But this has a major impact on
>> the logic. Another thing is overloaded methods.... not every language
>> allows them and doing a dispatch based on receiver/name is much more
>> easy to do. Ho many languages besides Groovy would get something out of
>> a MetaClass like this? On the other hand the ability to at last mutate a
>> MetaClass proofed to be an interesting application for Grails and
>> others. So at last a replacement for this must be given... but I guess
>> there is no real replacement for exchanging a MetaClass with a custom
>> version. So I can't answer if Groovy would migrate to that. It depends
>> on the outcome.
>
> The outcome depends on us. If we want it to be powerful and useful for
> Groovy (or JRuby, or Jython), we need only to snap our fingers...and
> then spend a few grueling days or weeks making it happen. But we're all
> going to spend that time anyway...it would be beneficial to share the load.
sure, I am not against that... I just think that making a version that
is more general than needed will cost performance, and a Groovy version
of the MetaClass might be the most general at all. I don't think there
is no point in for example Scheme handling overloaded methods.
This seems like a good way to go. Sure, there's a lot of call sites; but
we're talking about a reference each in a list somewhere, maybe 8 bytes
per call site. That pales in comparison to the actual call site adapter
itself.
>> Isn't it hidden from the user anyway? Or is groovy adding some features
>> that allow you to say "immediately after this method is called, replace
>> it with another implementation"? I've never heard of such a feature, but
>> I suppose it would have its uses.
>
> in Groovy it is not much hidden. Every GroovyObject has a setter for a
> MetaClass for example. We have a call back in the MetaClassRegistry
> allowing people to add custom meta classes and we have a loading rule
> that allows people to put precompiled MetaClasses... so it is quite
> open... and making its problems. The migration of the MOP will be heavy
> stuff for the Groovy Users, but I see no future in the current
> architecture. It is starting to make more problems than it solves.
That's an interesting viewpoint. I've wondered about it myself. It seems
like the ability to totally swap out a metaclass combined with the lack
of a command pattern would make optimizing Groovy very difficult.
So is this opening up the possibility of a backwards-incompatible change
in how MetaClass is used in Groovy, perhaps moving toward a 100%
"expando" model?
>> And yes, you'd have a reference to every call site, but every call site
>> could just call straight through to the cached code without doing
>> consistency checks. If consistency checking all the time can be made as
>> fast, I'd love to hear how.
>
> for a PIC you can't remove the checks to the parameters. At last I don't
> know how it should still work in a correct way then.
Sure, for a PIC you've always got some checking, but it may be simple
equality checks. If you have to check version information, that would
double the number of checks, since you'd have to do an equality check
and a version check for each type in turn.
> for foo2/bar. I guess it is inlined. But the other version consumes
> time. so even if it does not really mean much, it seems to give the
> inlining a problem... or something else for hotspot. Whatever it is, the
> time goes not near to 0. That was in server mode... in client mode my
> test shows that testReference is about 35 times faster.
Interesting...and this is in line with what understanding I have about
HotSpot. All the logic to wrap arguments represents a stumbling block on
the way to inlining code.
In general modifying JRuby's interpreted call path to support specific
arities isn't a big deal; it's modifying the compiler call path to
generate and call the appropriate specific-arity methods that I've
avoided. But your trivial benchmark seems to point at it being a
significant bottleneck, and Ruby methods are largely arity 1 or 2.
>> I would hope that many uses of the primitive wrappers can be picked up
>> by the JVM and optimized away, but perhaps that requires more static
>> typing and explicit compilation logic?
>
> I plan to add a primitive value calculation mode which is enabled if the
> MetaClass for Integer is unchanged. Then I will try to make the
> calculation using primitve types without the MetaClass as much as
> possible. This should give a massive speedup for integer calculation
> based tests. On the other hand... I know that a wrapper can be very fast
> as well if the most commonly used methods are defined on the wrapper and
> the runtime makes direct calls to these methods where possible. I have
> still to test how much difference in speed this might make.
Integer math optimizations are great for code where integer math is the
dominating job at runtime. However I don't generally write a lot of that
code myself, and I'd wager it's pretty rare. Don't get me wrong, integer
math ops are great, and they make for nice benchmark numbers. But I
wouldn't spend a lot of time on math optimizations if there are larger
problems to be solve in the overall method-dispatch logic.
>> The outcome depends on us. If we want it to be powerful and useful for
>> Groovy (or JRuby, or Jython), we need only to snap our fingers...and
>> then spend a few grueling days or weeks making it happen. But we're all
>> going to spend that time anyway...it would be beneficial to share the load.
>
> sure, I am not against that... I just think that making a version that
> is more general than needed will cost performance, and a Groovy version
> of the MetaClass might be the most general at all. I don't think there
> is no point in for example Scheme handling overloaded methods.
Then there's no need for Scheme to use this library. I don't think we
should try to pretend there's a way to build a one-size-fits-all runtime
like the DLR...rather I believe we should try to build as much in common
as possible, allowing for alternatives in each area.
As for the MOP work, I think it would generally apply best to dynamic
object-oriented languages, and it seems like that view is borne out by
the fact that multiple dynamic object-oriented language implementations
appear to be moving toward the same sort of MOP implementation.
- Charlie
It occurs to me now that this could be a source of overhead for Groovy.
So if I'm reading this right, if calling a method with a single
parameter, Groovy would always need to check if the incoming value is a
list before dispatching, in case the list could be unwrapped to a single
argument. Is that correct? If so, doesn't that mean that all invocations
of single-argument methods have to check if the argument is a list?
What about a case where there's both a single non-argument version and a
single list-argument version of a given method? Will passing a list of
one item end up calling the former or the latter?
It seems like abandoning implicit list expansion in favor of explicit
expansion would be a good idea; it would at least give you a
compile-time picture of whether expansion is necessary, and you wouldn't
have to check it for calls that will never expand.
- Charlie
kind of expando, yes. The ideas is to split the customizable part and
the fixed part, so we can optimize at last the regular case and loose
optimizations based on hte level of customization of the other part. And
there is a difference for memory too. Currently a MetaClass is normally
soft referenced, which means on low memory it is possible to throw away
the MetaClass unless it is hard referenced per suer request. But such an
approach makes version testing impossible, so maybe the fixed part will
be still soft referenced while the the customized part along with a stub
will not. The stub is then used as interface to the bytecode and allows
recreation on demand. But this needs tests. Current Groovy has a prolbme
because the MetaClass is too big, or at last it is too often discarded
on low memory. continuing recreation of the MetaClass takes too much
time, slowing down the application a lot. But I have not decided what to
do here in the end.
>>> And yes, you'd have a reference to every call site, but every call site
>>> could just call straight through to the cached code without doing
>>> consistency checks. If consistency checking all the time can be made as
>>> fast, I'd love to hear how.
>> for a PIC you can't remove the checks to the parameters. At last I don't
>> know how it should still work in a correct way then.
>
> Sure, for a PIC you've always got some checking, but it may be simple
> equality checks.
x.getClass() == expectedTypeForx
yes
> If you have to check version information, that would
> double the number of checks, since you'd have to do an equality check
> and a version check for each type in turn.
I need to check the version of the MetaClass for the receiver only, not
the parameters. And it is easy, because I compare only a cached number
from my cached MetaClass.
cachedVersion == cachedMetaClass.getVersion()
>> for foo2/bar. I guess it is inlined. But the other version consumes
>> time. so even if it does not really mean much, it seems to give the
>> inlining a problem... or something else for hotspot. Whatever it is, the
>> time goes not near to 0. That was in server mode... in client mode my
>> test shows that testReference is about 35 times faster.
>
> Interesting...and this is in line with what understanding I have about
> HotSpot. All the logic to wrap arguments represents a stumbling block on
> the way to inlining code.
>
> In general modifying JRuby's interpreted call path to support specific
> arities isn't a big deal; it's modifying the compiler call path to
> generate and call the appropriate specific-arity methods that I've
> avoided. But your trivial benchmark seems to point at it being a
> significant bottleneck, and Ruby methods are largely arity 1 or 2.
I was very surprised myself. I will have to run the test again to
simulate a pseudo method wrapper... interesting, looks like both
versions speed up a little, but testReference more than testArray, so it
is not 35 slower, but maybe 50 times. But... well, I would not give much
to the exact number, because 187ms is a too low number for a
measurement. But it shows the general ideas and it seems that the
wrapper is still inlined in both, client and server mode.
>>> I would hope that many uses of the primitive wrappers can be picked up
>>> by the JVM and optimized away, but perhaps that requires more static
>>> typing and explicit compilation logic?
>> I plan to add a primitive value calculation mode which is enabled if the
>> MetaClass for Integer is unchanged. Then I will try to make the
>> calculation using primitve types without the MetaClass as much as
>> possible. This should give a massive speedup for integer calculation
>> based tests. On the other hand... I know that a wrapper can be very fast
>> as well if the most commonly used methods are defined on the wrapper and
>> the runtime makes direct calls to these methods where possible. I have
>> still to test how much difference in speed this might make.
>
> Integer math optimizations are great for code where integer math is the
> dominating job at runtime. However I don't generally write a lot of that
> code myself, and I'd wager it's pretty rare. Don't get me wrong, integer
> math ops are great, and they make for nice benchmark numbers. But I
> wouldn't spend a lot of time on math optimizations if there are larger
> problems to be solve in the overall method-dispatch logic.
that optimization is only for politics, nothing else. For me current
Groovy is already fast enough.
>>> The outcome depends on us. If we want it to be powerful and useful for
>>> Groovy (or JRuby, or Jython), we need only to snap our fingers...and
>>> then spend a few grueling days or weeks making it happen. But we're all
>>> going to spend that time anyway...it would be beneficial to share the load.
>> sure, I am not against that... I just think that making a version that
>> is more general than needed will cost performance, and a Groovy version
>> of the MetaClass might be the most general at all. I don't think there
>> is no point in for example Scheme handling overloaded methods.
>
> Then there's no need for Scheme to use this library. I don't think we
> should try to pretend there's a way to build a one-size-fits-all runtime
> like the DLR...rather I believe we should try to build as much in common
> as possible, allowing for alternatives in each area.
>
> As for the MOP work, I think it would generally apply best to dynamic
> object-oriented languages, and it seems like that view is borne out by
> the fact that multiple dynamic object-oriented language implementations
> appear to be moving toward the same sort of MOP implementation.
ok, very well. I just wanted to make that part clear. It makes no sense
to work on something that misses the target audience then ;)
could? I would say: is ;)
> So if I'm reading this right, if calling a method with a single
> parameter, Groovy would always need to check if the incoming value is a
> list before dispatching, in case the list could be unwrapped to a single
> argument. Is that correct?
not a single, to multiple arguments, and then the dispatch is done again
using the multiple new arguments. But that step is done only if there
was no match with a list method.
> If so, doesn't that mean that all invocations
> of single-argument methods have to check if the argument is a list?
No, only if there was no match.
> What about a case where there's both a single non-argument version and a
> single list-argument version of a given method? Will passing a list of
> one item end up calling the former or the latter?
it will call the list version
> It seems like abandoning implicit list expansion in favor of explicit
> expansion would be a good idea;
something that we have too... *sigh*, my suggestion to remove this was
not approved.
> it would at least give you a
> compile-time picture of whether expansion is necessary, and you wouldn't
> have to check it for calls that will never expand.
I know.
Have you discussed the possibility of incompatible changes with the
community? I think you're going down a good path myself, but then I'm
not a groovy user and haven't become accustomed to the features that
might go away...
Given that you're largely doing a rewrite, how can I/we help?
> I need to check the version of the MetaClass for the receiver only, not
> the parameters. And it is easy, because I compare only a cached number
> from my cached MetaClass.
>
> cachedVersion == cachedMetaClass.getVersion()
But for every call, which means that in addition to the PIC type
comparison you've got several branches to deal with. If it's possible to
eliminate some of those branches, all the better.
I will grant that it's a pretty cheap test; it's the conditional logic
I'm more concerned about.
>> In general modifying JRuby's interpreted call path to support specific
>> arities isn't a big deal; it's modifying the compiler call path to
>> generate and call the appropriate specific-arity methods that I've
>> avoided. But your trivial benchmark seems to point at it being a
>> significant bottleneck, and Ruby methods are largely arity 1 or 2.
>
> I was very surprised myself. I will have to run the test again to
> simulate a pseudo method wrapper... interesting, looks like both
> versions speed up a little, but testReference more than testArray, so it
> is not 35 slower, but maybe 50 times. But... well, I would not give much
> to the exact number, because 187ms is a too low number for a
> measurement. But it shows the general ideas and it seems that the
> wrapper is still inlined in both, client and server mode.
After I take some Java integration performance problems, perhaps I'll
try to revisit the arity-specific call path again.
>> Integer math optimizations are great for code where integer math is the
>> dominating job at runtime. However I don't generally write a lot of that
>> code myself, and I'd wager it's pretty rare. Don't get me wrong, integer
>> math ops are great, and they make for nice benchmark numbers. But I
>> wouldn't spend a lot of time on math optimizations if there are larger
>> problems to be solve in the overall method-dispatch logic.
>
> that optimization is only for politics, nothing else. For me current
> Groovy is already fast enough.
Hah, ok. I can certainly understand that motivation :) Make fib run
faster!!!
- Charlie
It has long been acknowledged that users implementing custom
MetaClasses by subclassing Groovy's internal implementation of
MetaClassImpl is not a good way to go. User code that direclty sub
classed this internal representation has been breaking from release to
release for ages and we've never given any guaruantees about the
stability of this internal impelmentation
This is why we created ExpandoMetaClass
(http://groovy.codehaus.org/ExpandoMetaClass) which is a higher level
abstraction. When the internal representation (MetaClassImpl) is
re-written to get the performance/usability gains we can re-write how
ExpandoMetaClass maps onto that internal representation and hence no
user code that uses ExpandoMetaClass will break
Of course classes that subclass MetaClassImpl will have no guaruantees
(luckily in Grails we use ExpandoMetaClass exclusively :-)
Cheers
--
Graeme Rocher
Grails Project Lead
http://grails.org
We will make a big version jump to 2.0 for this to indicate the
incompatible change. We might go to java1.5 too for this.
> Given that you're largely doing a rewrite, how can I/we help?
hehe, helping with the concept helps much. But I think it is time to
start with some code now, to see what it is worth.
>> I need to check the version of the MetaClass for the receiver only, not
>> the parameters. And it is easy, because I compare only a cached number
>> from my cached MetaClass.
>>
>> cachedVersion == cachedMetaClass.getVersion()
>
> But for every call, which means that in addition to the PIC type
> comparison you've got several branches to deal with. If it's possible to
> eliminate some of those branches, all the better.
if the version does not match, the cache is recreated/flushed. a
MetaClass does not go back to an older version.
> I will grant that it's a pretty cheap test; it's the conditional logic
> I'm more concerned about.
I agree... well, maybe the listener ideas is not the worst. I would not
need that test anymore. And a part adding/removing/changing a method is
typically not performance relevant... so it would have no real negative
effect... hmm. Additionally the event can give information that helps to
minimize the recreation action for the cache. For example I need not to
recreate the cache if the changed method is not a possible match for the
cache. Or if these parameter classes are cached, then I could simply
replace the method wrapper. hmm...
[...]
>>> Integer math optimizations are great for code where integer math is the
>>> dominating job at runtime. However I don't generally write a lot of that
>>> code myself, and I'd wager it's pretty rare. Don't get me wrong, integer
>>> math ops are great, and they make for nice benchmark numbers. But I
>>> wouldn't spend a lot of time on math optimizations if there are larger
>>> problems to be solve in the overall method-dispatch logic.
>> that optimization is only for politics, nothing else. For me current
>> Groovy is already fast enough.
>
> Hah, ok. I can certainly understand that motivation :) Make fib run
> faster!!!
*g*
Thanks for the excellent clarification, Graeme. It doesn't mean people
won't complain if metaclass-swapping goes away, but the spoonful of
sugar could be greatly-improved performance. It will work out in the end.
On another note, how does this affect the JSR? Does anything in the
Groovy spec recommend or describe metaclass-swapping explicitly?
- Charlie
I'll leave that to Guillaume to answer in full at some point, but as
far as I'm aware the reference implementation Groovy 1.0/1.1 is out
and the spec and TCK are still pending
Cheers
In our case the key for flushing is the method wrapper object. When we
know it's going to be superceded (and dereferenced) by another change in
the hierarchy, we retrieve all those sites that have cached that
particular wrapper and tell them to flush. And as with most such caches,
the performance hit of iterating over all those sites is minimized by
the fact that most hierarchy mutations will have completed by the time
the call actually happens.
The "cache map" as it's called was actually written by Tom Enebo, and
it's pretty simple code:
http://svn.codehaus.org/jruby/trunk/jruby/src/org/jruby/runtime/CacheMap.java
In the version I used for call site cache invalidation, RubyModule was
replaced throughout with a "CacheSite" interface that implemented the
"removeCachedMethod" method. You get the idea.
- Charlie
- Charlie
>
> It seems like we're all trending toward the same state; the JRuby team
> is also considering and experimenting with a MOP for at least Java types
> and potentially for Ruby core types (to allow us to use java.lang.Long
> directly as a Ruby Fixnum, for example).
>
That's great -- it highlights one of the strengths of the approach, namely
the ability to use Java types (Long, Boolean, etc) directly. One of my
biggest problems was that I wasn't 100% sure about whether I'm on the
right track or trudging down a dead alley. At least now I know that even
if I'm in a dead alley, at least I'm in a good company :-)
> It also seems like work on a common MOP that could be reused across
> languages would be extremely useful, and we've all basically got the
> same requirements to meet when calling Java code.
Yeah, the idea is that people should be able to easily mix libraries
written in different languages when assembling their programs. In hands of
a resourceful developer, it makes one hell of a productivity boost if the
barriers between languages either don't exist or are small.
>> Full range of Java amenities, including optimized
>> runtime resolution of overloaded methods and support for JDK 1.5
>> variable
>> arguments is available.
>
> This is very interesting, and could be the key to allowing multiple
> languages to use the same MOP library.
>
Also, the "runtime resolution of overloaded methods and support for JDK
1.5 variable arguments" basically means a library-implemented
invokedynamic surrogate until we can all switch to JVMs that have one.
There are two ways to tackle this (and they even aren't mutually
exclusive!).
First way is to recognize that any sane language runtime implementation
will use its own MOP at the top of the MOP chain. Therefore, it can first
try to obtain an optimized call site object using an
implementation-specific API from it and only when that fails proceed with
general MOP lookup.
The second way is actually extension of the first way -- allow for an
optional MOP interface that can construct an object representing an
optimized call site. If all MOPs in the chain support it, then JRuby would
even be able to obtain optimized call sites for POJOs or Groovy objects
etc. The key is to come up with a clever way to implement this so as to
not hinder JITs inlining efforts. (Yes, I dare dream big right now --
we're just starting something, it's all quite malleable at this stage, so
we can bounce ideas around for a while).
I'm not sure though who'd have the responsibility to decide whether an
optimized call site is valid for subsequent invocations. I guess it'd be
the job of the language runtime to prove somehow that a call site is
reached with same types of arguments and refrain from using it if it
can't. The call site itself could probably also perform checks, but it's a
good question whether that'd kill the performance benefit.
(I'm aware there's suddenly plenty of replies in this thread -- I'm
replying as I go, so apologies if I'm rehashing something that's already
been said downstream in the thread; those thoughts are for now in my
subjective future even if they're in your subjective past :-) )
>
> It seems that your work, the existing Groovy MOP, the Ng MOP by John
> Wilson, and all the existing and theoretical dispatch and MOP work we're
> doing in JRuby are largely working toward the same goals. What would it
> take to get all of us working on this in the same place, under the JLR
> project?
Not much -- I'm not too attached to SourceForge for hosting. As long as
Google Code provides us with SVN access, and as long the licensing of the
work is either BSD or Apache, I'm okay. BTW, Sun has its own open source
project hosting at dev.java.net. Even JDK itself is being hosted there,
right? Seems like a logical choice for me. Did you consider it? (Sorry for
bringing this up again now, but I haven't had the chance to chime in into
the hosting discussions when they were relevant).
>
> I would commit to eventually migrating JRuby to this common MOP assuming
> it's feasible and we can all work together to improve it.
That's the idea -- that we all work together on improving the approach
until it's widely usable.
Attila.
>
> - Charlie
>
Since a few of your points are mentioned elsewhere in the thread, I'll
let you keep reading and replying for a bit before rehashing anything
myself :) I'll give it, say, a day?
>> It seems that your work, the existing Groovy MOP, the Ng MOP by John
>> Wilson, and all the existing and theoretical dispatch and MOP work we're
>> doing in JRuby are largely working toward the same goals. What would it
>> take to get all of us working on this in the same place, under the JLR
>> project?
>
> Not much -- I'm not too attached to SourceForge for hosting. As long as
> Google Code provides us with SVN access, and as long the licensing of the
> work is either BSD or Apache, I'm okay. BTW, Sun has its own open source
> project hosting at dev.java.net. Even JDK itself is being hosted there,
> right? Seems like a logical choice for me. Did you consider it? (Sorry for
> bringing this up again now, but I haven't had the chance to chime in into
> the hosting discussions when they were relevant).
It was brought up, and nobody was too excited about it. Myself, I hate
the UI at dev.java.net. I was also concerned about it not being seen as
"neutral (enough) ground", which is important for this kind of project.
- Charlie
I'd also make the assertion that it's library-level interop we want, and
that pervasive type-system interop is probably too ambitious for too
little real-world gain. Both Parrot and DLR work toward some level of
type-system unification, but it seems to cause more headaches than it cures.
> Also, the "runtime resolution of overloaded methods and support for JDK
> 1.5 variable arguments" basically means a library-implemented
> invokedynamic surrogate until we can all switch to JVMs that have one.
Even without installing this MOP framework into JRuby we are *extremely*
interested in the JLS-standard method selection capabilities. So that's
an immediate win.
> There are two ways to tackle this (and they even aren't mutually
> exclusive!).
>
> First way is to recognize that any sane language runtime implementation
> will use its own MOP at the top of the MOP chain. Therefore, it can first
> try to obtain an optimized call site object using an
> implementation-specific API from it and only when that fails proceed with
> general MOP lookup.
>
> The second way is actually extension of the first way -- allow for an
> optional MOP interface that can construct an object representing an
> optimized call site. If all MOPs in the chain support it, then JRuby would
> even be able to obtain optimized call sites for POJOs or Groovy objects
> etc. The key is to come up with a clever way to implement this so as to
> not hinder JITs inlining efforts. (Yes, I dare dream big right now --
> we're just starting something, it's all quite malleable at this stage, so
> we can bounce ideas around for a while).
I'm not sure I like the MOP having any real understanding of call sites,
other than potentially enough understanding to tell them to invalidate.
There are going to be lots of different call site optimizations possible
for different languages, so that seems like largely a separate problem
to be addressed outside the MOP.
Of course, if the "optimized call site" the MOP returns is really just a
data structure that can be used to synthesize multiple possible call
targets into a single inline cache, that would be fine too.
And we may be using slightly different understandings of "call site", so
this could be the same thing.
> I'm not sure though who'd have the responsibility to decide whether an
> optimized call site is valid for subsequent invocations. I guess it'd be
> the job of the language runtime to prove somehow that a call site is
> reached with same types of arguments and refrain from using it if it
> can't. The call site itself could probably also perform checks, but it's a
> good question whether that'd kill the performance benefit.
That seems to fit with the invokedynamic discussions, where the JVM
would simple want a "callable", would cache it, and would callback to
language-specific mechanisms for invalidating that cache. John Rose
could comment a bit more here on where we're going, but it seems like
this is the only fuzzy area for me right now.
>
> Right, time-spanned thread-local changes to metaclasses alleviate some
> of the problems, but as for a general solution this use case is too
> restrictive. Ideally a generic MOP would want to assume that all methods
> can always be overridden, and optimize for that case.
This is especially a concern in languages with no concept of a metaclass
-- see JavaScript :-)
>
> Generally, I don't think call-site caching semantics should even enter
> into the MOP implementation; the MOP should just return a callable
> object given a set of inputs, and the consumer of that callable should
> be responsible for caching it.
... and determining the scope of its validity. Completely agree.
>
>> but creating a new class per method sounds very silly and expensive. So
>> we might go with a Reflector as we do now. A class that consists mostly
>> of a table to call methods, just maybe a bit more improved.... such a
>> class could implement more than just one interface, or have one
>> reflector for each kind of call... keeping the total amount of generated
>> classes lower. I am still collecting ideas here.
>
> In both AOT and JIT mode, JRuby will generate a class per method, and we
> have not seen that it causes any severe impact to memory or performance
> (yet). Given that in a typical app run about half the called Ruby
> methods are currently getting compiled, it seems like it's not such a
> bad way to go. The overhead of having a class per method (and in our
> case, a *classloader* per method) is pretty gross, but it's manageable.
FWIW, Rhino does exactly the same -- one function, one class. Having a
classloader per function is necessary if you want to be able to garbage
collect on the granularity of individual function objects. Modern JVMs
don't have a problem with proliferation of class loaders.
Attila.
What I did is actually in no way very clever, it's very basic work, but
had to be done (best analogy I can come up with is SAX -- it's not very
clever in itself, but someone had to bite the bullet and lay down the
groundwork so people can write interoperable software atop of it). It's
basically a MOP interface spec for the operations on objects you find in
all dynamic languages -- get/put/delete properties, enumerate them, call
callables, coerce objects between types (for languages that allow implicit
type conversions). You can implement your own metaclass as a background
engine for MOP operations any way you wish (or even not implement it at
all if your language has no natural concept of classes, like none of
prototype based languages have -- see either Self or JavaScript for
example), my MOP approach won't constrain you in implementation in any
way. The whole point of my MOP approach is to provide a common set of
interfaces for JVM based dynamic languages so programs written in any of
them is able to manipulate properties, call callables, etc. on objects
originating from any other, without them needing to have explicit
knowledge of one another. (A nice side effect is that you also don't need
to provide "wrappers" for any object, including Java integers, booleans,
dates, etc). So, if you provide a MOP interface outward, other languages
will be able to use your objects. If you allow this interface to be used
in some manner while evaluating programs in your language runtime, you'll
be able to use other language's objects and invoke callables originating
from other languages. That's all. Of course, having such a common
interface can open up the way for further interops down the road, like
people writing clever mixin style libraries that as long as they only rely
on these MOP interfaces can operate on any language that exposes them,
providing unexpected benefits; that sort of artifacts usually crops up
sooner or later in an ecology based on common open source foundation :-)
Also, this being targetted for JVM, as a bonus feature I also provided a
very commonsensical MOP implementation for POJOs, which can be used as a
fallback MOP to handle any objects not otherwise recognized. But that's
really about all there is in it.
Attila.
--
home: http://www.szegedi.org
weblog: http://constc.blogspot.com
>
>
> I'd also make the assertion that it's library-level interop we want, and
> that pervasive type-system interop is probably too ambitious for too
> little real-world gain. Both Parrot and DLR work toward some level of
> type-system unification, but it seems to cause more headaches than it
> cures.
Well, it generates headaches as long as you mandate common interfaces for
those objects, which is what DLR folks are doing (the "One True Object"
approach). However, as soon as you don't need the common interface
("Scriptable", "IDynamicObject" "IOneTrueObject" or whatever), but rather
let the MOP perform operations on the objects, it ceases to be painful, as
you potentially become able to manipulate any java.lang.Object. Also, as
far as type system interop is concerned, if you add type coercing
capabilities into the MOP (as I did), you can effectively do things like
asking the MOP to coerce the object given to you as a predicate into a
Boolean within your "if" statement implementation. A Jython MOP will run
__nonzero__() on it for this purpose, and everything keeps working as
expected, even if the target code is JRuby when given a Jython object, as
long as JRuby uses a chained MOP with a Jython MOP implementation linked
into the chain somewhere.
>
>> Also, the "runtime resolution of overloaded methods and support for JDK
>> 1.5 variable arguments" basically means a library-implemented
>> invokedynamic surrogate until we can all switch to JVMs that have one.
>
> Even without installing this MOP framework into JRuby we are *extremely*
> interested in the JLS-standard method selection capabilities. So that's
> an immediate win.
>
Well, the code is there. Get it, use it, break it :-)
Honestly, you gave way much thought to this issue so far than I did, so
I'm inclined to just trust you for now on it. I don't feel like a equal
party for conversing in this particular topic at the moment, so I'd rather
not just make noise :-)
Attila.
yes... but... for example when I get a property, how do I handle things
such as super? In Groovy for example a property can be represented by a
method, for example foo by getFoo(). But if I do super.foo, then I don't
want to call getFoo form the current class, I want to call getFoo from
the super class. On the other hand foo could be also a normal field...
Another possibility is to think about further extensions to MOP, ones that
allow you to express concepts like that in a rather generic manner. I'd
like to be very clear that the whole "common MOP foundation" business is
very far from being set in stone; it's there primarily as a vehicle for
drawing people into participation toward its final shape, so it's open to
even very radical change.
I.e. one flying idea that immediately comes to mind is that if you're
worried about repeatedly having to check whether a name starts with
"super.", we could introduce a feature for mapping string names to opaque
identifiers (that are quicker to lookup internally), much the same as how
Objective-C selectors work.
Attila.
On Thu, 09 Aug 2007 16:47:23 +0200, Jochen Theodorou <blac...@gmx.org>
wrote:
>
> Attila Szegedi schrieb:
> [...]
> > It's
>> basically a MOP interface spec for the operations on objects you find in
>> all dynamic languages -- get/put/delete properties, enumerate them, call
>> callables, coerce objects between types (for languages that allow
>> implicit
>> type conversions).
>
> yes... but... for example when I get a property, how do I handle things
> such as super? In Groovy for example a property can be represented by a
> method, for example foo by getFoo(). But if I do super.foo, then I don't
> want to call getFoo form the current class, I want to call getFoo from
> the super class. On the other hand foo could be also a normal field...
>
> bye blackdrag
>
--
Ok, I've had a look through the code, and it's roughly in line with what
I expected. Here's some thoughts:
- I love to see DynamicMethod and OverloadedDynamicMethod. We already
have a nearly identical DynamicMethod in JRuby that is the interface to
all callable "method" objects. And I once created an
OverloadedDynamicMethod to handle splitting type-specific variants of
Ruby methods into individual implementations. Small world.
- The problem I mentioned earlier does exist in this version of the MOP,
namely that it does all the dispatch itself rather than returning some
sort of callable that could be cached at the call site. With the current
interfaces, there's no way to do call site optimizations. But it's a
pretty simple interface, and the changes required wouldn't be difficult.
For example, if we just added:
MetaObjectProtocol:
public Callable getCallable(Object callableId);
new ifc Callable:
public Object call(Object target, Object callableId, Object[] args,
Marshaller m)
DynamicMethod and MetaObjectProtocolBase (or similar places):
implements Callable
Then we can do the following:
- getCallable for language impls that produce callable objects (like
JRuby) could return those objects, implementing Callable; Callable.call
would dispatch to the actual code as appropriate.
- getCallable for language impls that don't produce callable objects
(like Groovy) would just return the metaclass directly. Callable.call
would be normal metaclass dispatch as today in Groovy.
- The base impls of MOP would just implement call as getCallable().call
(since callable objects seems to be the norm, rather than the exception;
see Rhino, JRuby, Jython, others).
Given this, most call sites would do getCallable().call as well, and
would have the option to cache appropriately. Hell, different languages
could even cache other languages' callables, provided we also develop a
common call-site caching mechanism (out of scope for this MOP talk).
I could see wiring something like the above into JRuby almost
immediately; it's a fairly trivial thing to implement these interfaces
and start using them, provided I can still do my call site optz.
- Charlie
You seem to misunderstand what DLR is doing. DLR *is* a MOP that
performs operations on plain System.Object. IDynamicObject is
implemented by MOP implementations, not by objects themselves.
--
Seo Sanghyeon
[*] I couldn't verify this unfortunately. I read the article
<http://blogs.msdn.com/hugunin/archive/2007/05/04/the-one-true-object-part-2.aspx>
again, and he mentions "CreateInstance" as a message to the object --
this'd imply he thinks about IDynamicObject being implemented on a
metaobject protocol instance, instead of on all instances manipulated by
DLR. (As normally, you'd want CreateInstance to be defined on a MOP,
however this is again not necessarily the case -- see JavaScript functions
being used as constructors, right?) I also downloaded the latest
IronPython 2.0A3m, but it isn't too straightforward to figure out from
Microsoft.Scripting.IDynamicObject whether it's meant to be implemented by
classes whose instances are meant to be scriptable, or on MOP objects. Can
you help clarifying the DLR architecture behind IDynamicObject?
Attila.
On Wed, 15 Aug 2007 02:21:21 +0200, Sanghyeon Seo <san...@gmail.com>
wrote: