0

Lets say I'm writing a deserialization utility, and would like to have performance better than Reflection.

I have a Map<Class, MethodHandle>, where the MethodHandle is the desired constructor for the given Class. When a user calls T deserialize(ByteBuffer, Class<T>), it gets the respective MethodHandle from the Map, gets the constructor arguments from the buffer, and then calls MethodHandle::invoke. And this works fine.

The thing is, unless a MethodHandle is static final, then performance is equivalent to reflection.

Is there a way to use LambdaMetaFactory, or MutableCallSite to improve performance? I don't see how LambdaMetaFactory can be used (we need to know the interface type ahead of time?), and creating a MutableCallSite would still not be static final.

kantianethics
  • 671
  • 5
  • 21
  • 1
    Nothing you invest here will pay off unless you’re going to call the resulting function a significant number of times. Your explanation suggests the opposite; you have a `Map` with potentially a lot of handles and all your invocations are distributed over these handles. – Holger Feb 10 '20 at 17:46
  • How many is too many? 100? The MethodHandle in my Map could change to the lambda/interface generated by LambdaMetaFactory. – kantianethics Feb 10 '20 at 18:03
  • 1
    How long does it take to call a `MethodHandle` 100 times? That’s the maximum you could ever save (obviously, the actual saving will be smaller). Then, consider how long will it take to generate whatever kind of accessor… – Holger Feb 10 '20 at 18:05
  • I hear you, what I am trying to say is that there will be a small number of constructor MethodHandles, and that they will be invoked very many times. I will benchmark of course, but if there is a way to get the performance closer to invoking a `final static MethodHandle`, I would like to try. – kantianethics Feb 10 '20 at 18:13
  • 1
    Do they have a similar signature? You need a pre-existing interface type to use the `LambdaMetaFactory`, but that’s needed anyway, as in the end, you need something to make the call against. The less handles you have, the less the problem… – Holger Feb 10 '20 at 18:21
  • I will not know the classes to deserialize ahead-of-time. but every MethodHandle will be a constructor. – kantianethics Feb 10 '20 at 18:29
  • 1
    The classes do not matter, the parameter lists of the constructors matter. In the worst case, we could even cope with arbitrary parameter types, as long as the constructors have the same number of parameters. The `LambdaMetaFactory` can adapt types (like auto-(un)boxing and calling generic code after type erasure), but can not do varargs. – Holger Feb 10 '20 at 18:34
  • Ah, I see how it is easier to use lambdametafactory for setter deserialization now... there's always one parameter. I won't be able to assumer parameter number/type about the classes, so maybe I should require POJO-style setters. But I'd much prefer to just use a constructor. – kantianethics Feb 10 '20 at 18:50
  • 1
    The parameter is that `ByteBuffer`? – Holger Feb 10 '20 at 19:00
  • Yes, the ByteBuffer holds the serialized data. The serialization format makes it easy to determine the name, type, and value of each field, similar to JSON. Since the code is compiled with `-parameters`, I can then map to constructor parameters, like the Jackson ParameterNames module. – kantianethics Feb 10 '20 at 19:13
  • 1
    So, to get this correctly, the constructors have different parameters, it’s the factory which receives a `ByteBuffer` and is supposed to decode it and invoke the constructor with the right parameters? – Holger Feb 10 '20 at 19:17
  • Yes, this is correct – kantianethics Feb 10 '20 at 19:18
  • 2
    Then, `LambdaMetaFactory` can only help when you have an interface for every possible parameter type list. Or you take the more complex path of generating the code to extract the arguments and invoke the constructor yourself. – Holger Feb 10 '20 at 19:22
  • Ok, really appreciate the help. I'd be fine with just MethodHandle::invoke, when a MethodHandle is `static final` performance is amazing. Can't wait for https://bugs.openjdk.java.net/browse/JDK-8233873 . Cheers! – kantianethics Feb 10 '20 at 19:31
  • Starting to wonder if ConstantDynamic could solve the `static final` methodhandle problem... will keep researching. https://www.youtube.com/watch?v=knPSQyUtM4I&list=LL-UEtpa6B-c2QQpU0LUDXFA&index=2&t=19m40s – kantianethics Feb 16 '20 at 05:29
  • 1
    Each distinct `ldc` instruction can only be used to load exactly one constant value. That’s not much helpful. Or not better than using `invokedynamic`. When the `invokedynamic` instruction gets permanently linked to a `MethodHandle`, it works as smooth as a `static final` handle, but you need one instruction per handle. Or, well, you can relink a few number of times, getting deoptimized and optimized again, but after some threshold, it will be turned into a polymorphic call site. There is no solution for a `Map` like scenario, unless you can find a common interface for it. – Holger Feb 17 '20 at 09:03
  • But since we're not transforming a MethodHandle like indy, and only trying to load an existing one from the constant pool, we could create the Map where the DynamicConstantDesc is built via reflection on the first invocation. Then, we load it with Intrinsics.ldc (https://openjdk.java.net/jeps/303). Yes each ldc can only load one constant value... but this is what will help us I think! – kantianethics Feb 17 '20 at 12:45
  • 2
    `invokedynamic` does not transform a method handle. It invokes a bootstrap method on its first invocation and gets linked to the returned `MethodHandle`, which it will then invoke an each evaluation. So an `ldc` leading to a handle, followed by invoking the handle is pretty much the same, except that there’s again the risk that the JIT doesn’t recognize the predictable nature of the invocation, whereas `invokedynamic` is know to work as intended. If JEP 303 will ever materialize, using it for indy would make more sense for your case. – Holger Feb 17 '20 at 15:29
  • You are right. I think Intrinsics::invokedynamic() of JEP 303 is needed to call arbitrary constructors then. – kantianethics Feb 20 '20 at 22:20

0 Answers0