2

I know from the Faster alternatives to Java's reflection, Using LambdaMetafactory with MethodHandle is closer to a direct call, but in my JMH program results, LambdaMetafactory is even slower than reflection, It's quite different from the previous questions. I don't know what went wrong

Here are my JMH test results,the BenchmarkMode is AverageTime

Benchmark                                      Mode  Cnt     Score     Error  Units
MhExceptionBenchMark.MhExceptioTest.directNew  avgt   10   248.059 ±  52.375  ns/op
MhExceptionBenchMark.MhExceptioTest.mhLamda    avgt   10  2435.962 ±  45.767  ns/op
MhExceptionBenchMark.MhExceptioTest.mhNoLamda  avgt   10  2573.739 ± 688.203  ns/op
MhExceptionBenchMark.MhExceptioTest.reflet     avgt   10  2344.597 ± 237.946  ns/op

All I have to do is dynamically build a constructor with a String input and assign the value message to it

This class is called ValidException

public class ValidException extends AbstractException {

    private static final long serialVersionUID = 1L;

    private static final String DEFAULT_VALID_ERRCODE = "test";

    public ValidException() {
        super(DEFAULT_VALID_ERRCODE);
    }

    public ValidException(String errMessage) {
        super(DEFAULT_VALID_ERRCODE, errMessage);
    }
}

Its parent class is AbstractException

public abstract class AbstractException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    private String errMessage;

    public AbstractException(String errMessage) {
        super(errMessage);
    }
}

My JMH program is

@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 3)
@Measurement(iterations = 10, time = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(10)
public class MhExceptioTest {

    @State(Scope.Benchmark)
    public static class Ex {
        ValidException validException;

        @Setup(Level.Trial)
        public void prepare() {
            validException = new ValidException();
        }
    }

    @State(Scope.Benchmark)
    public static class MhNoLambda{
        MethodHandle methodHandle;

        @Setup(Level.Trial)
        public void prepare() throws NoSuchMethodException, IllegalAccessException {
            MethodType methodType = MethodType.methodType(void.class, String.class);
            methodHandle = MethodHandles.publicLookup().findConstructor(ValidException.class, methodType);
        }
    }

    @State(Scope.Benchmark)
    public static class MhLambda{
        Function<String,AbstractException> function;

        @Setup(Level.Trial)
        public void prepare() throws Throwable {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            MethodType methodType = MethodType.methodType(void.class, String.class);
            MethodHandle methodHandle = lookup.findConstructor(ValidException.class, methodType);
            CallSite site = LambdaMetafactory.metafactory(
                    lookup,
                    "apply",
                    MethodType.methodType(Function.class),
                    methodHandle.type().generic(),
                    methodHandle,
                    methodHandle.type());
            function = (Function<String, AbstractException>)site.getTarget().invokeExact();
        }
    }

    @State(Scope.Benchmark)
    public static class Constr{
        Constructor<ValidException> constructor;

        @Setup(Level.Trial)
        public void prepare() throws NoSuchMethodException {
            constructor = ValidException.class.getConstructor(String.class);
        }
    }


    @Benchmark
    public <T extends AbstractException> AbstractException directNew(Ex ex){
        ValidException validException = ex.validException;
        validException.setErrMessage("test");
        return validException;
    }

    @Benchmark
    public <T extends AbstractException> AbstractException mhNoLamda(MhNoLambda mhNoLambda) throws Throwable {
        return (AbstractException) mhNoLambda.methodHandle.invoke("test");
    }

    @Benchmark
    public <T extends AbstractException> AbstractException reflet(Constr constr) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        ValidException validException = constr.constructor.newInstance("test");
        return validException;
    }

    @Benchmark
    public <T extends AbstractException> AbstractException mhLamda(MhLambda mhLambda){
        return mhLambda.function.apply("test");
    }

    public static void main(String[] args) throws RunnerException {
        Options options = new OptionsBuilder()
                .include(MhExceptioTest.class.getSimpleName())
                .result("./result-mh-ex.json")
                .resultFormat(ResultFormatType.JSON)
                .build();
        new Runner(options).run();
    }
}
benym
  • 29
  • 3
  • 2
    One observation: your `directNew()` method isn't creating a new exception on each call but reuses the single one you created for your benchmark and sets the text. So this method is not comparable to the others. – Thomas Dec 06 '22 at 07:35
  • Quickly comparing your code with the linked one I see one call that's missing in your's and that might make a difference: `MethodHandle mh=lookup.unreflect(reflected);` – Thomas Dec 06 '22 at 07:38
  • Reusing the instance is to smooth out the initialization work, I also tried to directly new out the entity, his test performance is `MhExceptionBenchMark.MhExceptioTest.directNew avgt 10 2346.328 ± 17.985 ns/op` – benym Dec 06 '22 at 07:41
  • That looks more right to me. After all, if you want to test performance of creating new exceptions each test should do so. Note that creating an object requires more work for the JVM than just calling a method or constructor so the performance differences probably won't be so high. – Thomas Dec 06 '22 at 08:02
  • Yes, the other two methods were initialized to be fair to see how dynamic assignment performance would be if the Constructor were created directly. I just used `Constructor constructor = cls.getConstructor(String.class);MethodHandle methodHandle = lookup.unreflectConstructor(constructor);`to initialize the lookup, but the performance gets worse after testing, which doesn't solve my mystery – benym Dec 06 '22 at 08:24
  • If I have a lot of these entities that need to be created dynamically, I can actually cache them so that the overhead of creating the entity is gone, just the overhead of calling the set method, which gives me something like `248.059 ± 52.375 ns/op`, And LambdaMetafactory metafactory only cache corresponding to the `Function`, the real instance is created in the `apply`, seems to be the scene don't do faster than new directly from the object? – benym Dec 06 '22 at 08:34
  • 1
    Given the huge value of `Error` in the results, it's impossible to conclude that one method is faster than another. You need to stabilize your benchmark first. – apangin Dec 07 '22 at 00:07
  • 2
    Anyway, there is no sense in measuring the reflection costs of exception instantiation, when 99% time is spent on collecting the exception stack trace. – apangin Dec 07 '22 at 00:11
  • 2
    The most expensive aspect of an exception construction, is the stack trace, so there’s no point in comparing it to the performance of setting the message. Worth reading: [The Exceptional Performance of Lil' Exception](https://shipilev.net/blog/2014/exceptional-performance/) The error of mhNoLamda is three times the difference to the other methods, so obviously that is not a reliable result anyway. – Holger Dec 09 '22 at 17:42
  • After your answers, I probably understand that this kind of instantiated scenario is relatively meaningless. Come to think of it, that's true. Thank you everyone. – benym Dec 15 '22 at 09:51

0 Answers0