From 557aa4971429961121ae363b58fd3c64641687f8 Mon Sep 17 00:00:00 2001 From: James Yuzawa Date: Mon, 10 Apr 2023 19:48:10 -0400 Subject: [PATCH] Cache constructor MethodHandles in factories Update LocalCacheFactory.java --- .../benmanes/caffeine/FactoryBenchmark.java | 40 ++++++++++++++----- .../caffeine/cache/LocalCacheFactory.java | 22 ++++++++-- .../benmanes/caffeine/cache/NodeFactory.java | 22 ++++++++-- .../caffeine/cache/BoundedLocalCacheTest.java | 1 - 4 files changed, 66 insertions(+), 19 deletions(-) diff --git a/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java b/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java index 00f87b8c4b..67408119ff 100644 --- a/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java +++ b/caffeine/src/jmh/java/com/github/benmanes/caffeine/FactoryBenchmark.java @@ -15,14 +15,15 @@ */ package com.github.benmanes.caffeine; +import java.lang.invoke.LambdaMetafactory; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; - import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.infra.Blackhole; /** * @author ben.manes@gmail.com (Ben Manes) @@ -34,23 +35,28 @@ public class FactoryBenchmark { private final MethodHandleFactory methodHandleFactory = new MethodHandleFactory(); @Benchmark - public Alpha direct() { - return new Alpha(); + public void direct(Blackhole blackhole) { + blackhole.consume(new Alpha()); } @Benchmark - public Alpha methodHandle_invoke() { - return methodHandleFactory.invoke(); + public void methodHandle_invoke(Blackhole blackhole) { + blackhole.consume(methodHandleFactory.invoke()); } @Benchmark - public Alpha methodHandle_invokeExact() { - return methodHandleFactory.invokeExact(); + public void methodHandle_invokeExact(Blackhole blackhole) { + blackhole.consume(methodHandleFactory.invokeExact()); } @Benchmark - public Alpha reflection() { - return reflectionFactory.newInstance(); + public void methodHandle_lambda(Blackhole blackhole) { + blackhole.consume(methodHandleFactory.lambda()); + } + + @Benchmark + public void reflection(Blackhole blackhole) { + blackhole.consume(reflectionFactory.newInstance()); } static final class MethodHandleFactory { @@ -58,11 +64,17 @@ static final class MethodHandleFactory { private static final MethodType METHOD_TYPE = MethodType.methodType(void.class); private final MethodHandle methodHandle; + private final AlphaConstructor lambda; MethodHandleFactory() { try { methodHandle = LOOKUP.findConstructor(Alpha.class, METHOD_TYPE); - } catch (NoSuchMethodException | IllegalAccessException e) { + lambda = + (AlphaConstructor) LambdaMetafactory + .metafactory(LOOKUP, "construct", MethodType.methodType(AlphaConstructor.class), + methodHandle.type(), methodHandle, methodHandle.type()) + .getTarget().invokeExact(); + } catch (Throwable e) { throw new RuntimeException(e); } } @@ -82,6 +94,10 @@ Alpha invokeExact() { throw new RuntimeException(e); } } + + Alpha lambda() { + return lambda.construct(); + } } static final class ReflectionFactory { @@ -107,4 +123,8 @@ Alpha newInstance() { static final class Alpha { public Alpha() {} } + + private interface AlphaConstructor { + Alpha construct(); + } } diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java index 58b5fa5e02..d605469d39 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/LocalCacheFactory.java @@ -18,6 +18,8 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.checkerframework.checker.nullness.qual.Nullable; @@ -30,6 +32,7 @@ final class LocalCacheFactory { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final MethodType FACTORY = MethodType.methodType( void.class, Caffeine.class, AsyncCacheLoader.class, boolean.class); + private static final Map CONSTRUCTORS = new ConcurrentHashMap<>(); private LocalCacheFactory() {} @@ -41,7 +44,7 @@ static BoundedLocalCache newBoundedLocalCache(Caffeine builde } static String getClassName(Caffeine builder) { - var className = new StringBuilder(LocalCacheFactory.class.getPackageName()).append('.'); + var className = new StringBuilder(); if (builder.isStrongKeys()) { className.append('S'); } else { @@ -80,10 +83,21 @@ static String getClassName(Caffeine builder) { static BoundedLocalCache loadFactory(Caffeine builder, @Nullable AsyncCacheLoader cacheLoader, boolean async, String className) { + var constructor = CONSTRUCTORS.get(className); + if (constructor == null) { + constructor = CONSTRUCTORS.computeIfAbsent(className, LocalCacheFactory::newConstructor); + } + try { + return (BoundedLocalCache) constructor.invoke(builder, cacheLoader, async); + } catch (Throwable t) { + throw new IllegalStateException(className, t); + } + } + + static MethodHandle newConstructor(String className) { try { - Class clazz = Class.forName(className); - MethodHandle handle = LOOKUP.findConstructor(clazz, FACTORY); - return (BoundedLocalCache) handle.invoke(builder, cacheLoader, async); + Class clazz = Class.forName(LocalCacheFactory.class.getPackageName() + "." + className); + return LOOKUP.findConstructor(clazz, FACTORY); } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { diff --git a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java index 162fc27911..512eade4c7 100644 --- a/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java +++ b/caffeine/src/main/java/com/github/benmanes/caffeine/cache/NodeFactory.java @@ -19,6 +19,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import com.github.benmanes.caffeine.cache.References.LookupKeyReference; import com.github.benmanes.caffeine.cache.References.WeakKeyReference; @@ -31,6 +33,7 @@ interface NodeFactory { MethodType FACTORY = MethodType.methodType(void.class); MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + Map CONSTRUCTORS = new ConcurrentHashMap<>(); RetiredStrongKey RETIRED_STRONG_KEY = new RetiredStrongKey(); RetiredWeakKey RETIRED_WEAK_KEY = new RetiredWeakKey(); @@ -87,7 +90,7 @@ static NodeFactory newFactory(Caffeine builder, boolean isAsy } static String getClassName(Caffeine builder, boolean isAsync) { - var className = new StringBuilder(Node.class.getPackageName()).append('.'); + var className = new StringBuilder(); if (builder.isStrongKeys()) { className.append('P'); } else { @@ -132,10 +135,21 @@ static String getClassName(Caffeine builder, boolean isAsync) { } static NodeFactory loadFactory(String className) { + var constructor = CONSTRUCTORS.get(className); + if (constructor == null) { + constructor = CONSTRUCTORS.computeIfAbsent(className, NodeFactory::newConstructor); + } + try { + return (NodeFactory) constructor.invoke(); + } catch (Throwable t) { + throw new IllegalStateException(className, t); + } + } + + static MethodHandle newConstructor(String className) { try { - Class clazz = Class.forName(className); - MethodHandle handle = LOOKUP.findConstructor(clazz, FACTORY); - return (NodeFactory) handle.invoke(); + Class clazz = Class.forName(Node.class.getPackageName() + "." + className); + return LOOKUP.findConstructor(clazz, FACTORY); } catch (RuntimeException | Error e) { throw e; } catch (Throwable t) { diff --git a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java index 68475b5bc0..0142ed06e1 100644 --- a/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java +++ b/caffeine/src/test/java/com/github/benmanes/caffeine/cache/BoundedLocalCacheTest.java @@ -50,7 +50,6 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong;