public class Function3 {
public static final int FOO = 1;
public static final int BAR = 2;
public static final int BAZ = 3;
private int which = 0;
public static final Function3 foo = new Function3(FOO);
public static final Function3 bar = new Function3(BAR);
public static final Function3 baz = new Function3(BAZ);
private Function3(which) { this.which = which; }
private static Object foo(arg1, arg2, arg3) = { ... }
private static Object bar(arg1, arg2, arg3) = { ... }
private static Object baz(arg1, arg2, arg3) = { ... }
public Object invoke(arg1, arg2, arg3) {
switch (which) {
case FOO: foo(arg1, arg2, arg3); break;
case BAR: bar(arg1, arg2, arg3); break;
case BAZ: baz(arg1, arg2, arg3); break;
default: throw new MethodInvocationError("can't happen");
}
}
}
Now I'm wondering if I shouldn't just use Method objects and
Method.invoke(). I know in the bad old days that was very slow, but
how fast is it on modern JVMs compared to the above? A
one-class-per-function solution is, I think, prohibitive.
--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures
How about:
public abstract class Function3 {
public static final Function3 foo = new Function3() {
public Object invoke(Object arg1, Object arg2, Object arg3) {
return foo(arg1, arg2, arg3);
}
};
public static final Function3 bar = new Function3() {
public Object invoke(Object arg1, Object arg2, Object arg3) {
return bar(arg1, arg2, arg3);
}
};
public static final Function3 baz = new Function3() {
public Object invoke(Object arg1, Object arg2, Object arg3) {
return baz(arg1, arg2, arg3);
}
};
private static Object foo(Object arg1, Object arg2, Object arg3) = { ... }
private static Object bar(Object arg1, Object arg2, Object arg3) = { ... }
private static Object baz(arg1, arg2, arg3) = { ... }
public abstract Object invoke(Object arg1, Object arg2, Object arg3);
}
John Wilson
This is just the one-class-per-function design under a new disguise.
OK.
I have found that the reflection is about an order of magnitude slower
that direct method calls using JDK 6 with the -server option. However
the overhead is quite sensitive to the path from the call site to the
call of Method.invoke(). The JIT appears to like as few stack frames
as possible between the call site and the reflection call.
Object doCall() {
myMethod.invoke();
}
Method getMyCall() {
return myCall;
}
...
doCall() // slower
getMyCall().invoke() // faster
...
In my runtime the dispatch could happen three of four levels down and
returning the Method made a difference. If you are only one level down
you may not see a big advantage.
Also note that the performance of reflection has got better in later
JDKs so if there is a change that your language might have substantial
use on older JVMs then reflection becomes less interesting.
My experience is that you have to spend considerable energy in looking
at the execution paths through the runtime system ensuring that they
are simple (the prime requirement) and short ( a secondary
requirement).
One example:
I have a situation in which monkey patching can change the destination
of a call. In the MetaClass object which determines the behaviour of
the class I have a reference to a MetaClass which is used when there
has been a monkey patch to point to the new behaviour.
Originally I wrote this:
class MetaClass {
private MetaClass actualMetaClass;
public MetaClass() {
this. actualMetaClass = this;
}
public monkeyPatch(MetaClass newMetaClass) {
this.actualMetaClass = newMetaClass;
}
public Object invokeMethod() {
return this.actualMetaClass.doInvokeMethod();
}
public Object doInvokeMethod() {....
This was OK but rewriting the code like this:
class MetaClass {
private MetaClass actualMetaClass = null;
public monkeyPatch(MetaClass newMetaClass) {
this.actualMetaClass = newMetaClass;
}
public Object invokeMethod() {
if (this.actualMetaClass == null) {
return doInvokeMethod();
} else {
return this.actualMetaClass.doInvokeMethod();
}
}
public Object doInvokeMethod() {....
Made a substantial improvement in performance.
It appears that the JIT compiler can do more inlining in the second case.
If I was able (and, in general I'm not) I'd use one class per function
if I wanted the very best performance.
John Wilson
> In my language, I need objects which represent Java static methods.
That's what method handles are for; from a JDK 7 viewpoint you are
asking what is the best pre-JDK7 way to emulate method handles.
Rémi Forax has a backport project which will reweave your JDK7
bytecodes into a form suitable for pre-7 JVMs.
That pre-7 form generated by the backport uses about the same
expedients you are currently using. The nice thing about that is it
can serve as a collection point for best practices. I think grouping
methods in a switch is the state of the art before JDK 7.
Depending on your schema of call types, you'll want to choose between
grouping call types on interface types vs. one call type per interface
type. Interface types can be true Java interfaces, or Java abstract
classes with factored default error-reporting or adapting behaviors.
Any of those choices is usually preferable to plain reflection,
because the semantics of reflective invocation is very complex, as it
includes:
- wrapped individual arguments and return values
- wrapped varargs list
- wrapped exceptions
- per-call access checks with associated exceptions
Also, reflection does not support the semantics of invokespecial. And
there may be a footprint cost, as reflective objects are not mere
invocation capabilities (like method handles). They carry extra
baggage around to support symbol table queries such as annotations,
throws clauses, and Java generic signatures.
I hope this helps.
-- John
> Where can I find documentation on the JDK 7 changes to bytecodes and
> the backport project?
The next build of OpenJDK (b58) will contain a provisional API derived
on last year's JSR 292 EDR. The javadocs in that build will be useful.
The format of the invokedynamic bytecode has changed since last year's
EDR. There are no public specs. yet for the latest version, but (if
it helps) you can look at the javac backend for the invokedynamic
support, again in in OpenJDK build 58. The sources are viewable as of
this week:
http://hg.openjdk.java.net/jdk7/tl/langtools/file/tip/src/share/classes/com/sun/tools/javac/jvm/Code.java
(search for invokedynamic)
The 292 EG owes the world another EDR; I'm the bottleneck for that and
am working on that also.
-- John
> I think grouping
> methods in a switch is the state of the art before JDK 7.
Okay. *sigh*
> Depending on your schema of call types, you'll want to choose between
> grouping call types on interface types vs. one call type per interface
> type.
Since the arguments and returns are all Object, I think all I need is
one class per arity: Function0, Function1, Function2, ... FunctionN,
which last takes an Object[] of arguments.
> Interface types can be true Java interfaces, or Java abstract
> classes with factored default error-reporting or adapting behaviors.
I don't follow you. Can you explain how this fits with the model I
posted? (Note that the static methods don't actually have to be in
this class at all; that was just the simplest thing pedagogically.) I
think you mean that the Functionx classes can be a subclass of an
abstract Function class which implements throwing versions of invoke0,
invoke1, invoke2, ... invoken, so as to catch calls with too many or
too few arguments. Is there something I'm missing?
- javac doesn't want to find anything that is inside java.dyn:
fr/umlv/indy/visitor/test/Main.java:3: package java.dyn does not exist
import java.dyn.CallSite;
It's weird because java.dyn.* classes are in rt.jar.
And it works if the classpath is set to rt.jar
javac -source 1.7 -cp /usr/java/jre/lib/rt.jar:. -d ../build/classes/
fr/umlv/indy/visitor/test/Main.java
[...]
> -- John
>
Rémi
I'd recommend having a Function<ARG_T extends ArgumentType> and abstract away the argument typing.
~~ Robert.
> I seems there are some problem with b58 integration :
> - the public javadoc doesn't contains java.dyn package.
> http://download.java.net/jdk7/docs/api/
I made an error (in configuration management) in the makefile which
controls which APIs are officially visible. We're fixing this in b59.
-- John