Skip to content

Commit c110475

Browse files
authored
[6.2.x] Improve cache performance (#84)
Reduces the latency of NoLoader posting in CacheCopy and CacheConcurrent modes by about 14% and 18%, respectively.
1 parent 1cac642 commit c110475

File tree

13 files changed

+84
-84
lines changed

13 files changed

+84
-84
lines changed

eventbus-test/src/test/java/net/minecraftforge/eventbus/test/map/MapTestBase.java

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@
99
import java.util.Map;
1010
import java.util.Set;
1111
import java.util.concurrent.ExecutorService;
12-
import java.util.function.BiFunction;
1312
import java.util.function.Function;
14-
import java.util.function.Supplier;
1513

16-
import net.minecraftforge.eventbus.InternalUtils;
14+
import net.minecraftforge.eventbus.internal.Cache;
1715
import net.minecraftforge.eventbus.test.Whitebox;
1816

1917
public class MapTestBase {
@@ -40,7 +38,7 @@ protected <K, V> Map<K, V> cache(String type) {
4038

4139
// Hacky because I dont want to deal with implementing the entire Map interface
4240
protected static class MapLike<K, V> implements Map<K, V> {
43-
private final BiFunction<K, Supplier<V>, V> lock = InternalUtils.cachePublic();
41+
private final Cache<K, V> lock = Cache.create();
4442
@SuppressWarnings("unchecked")
4543
private final Function<K, V> get = Whitebox.getMethod(lock, "get", (Class<K>)Object.class);
4644

@@ -51,15 +49,15 @@ public V get(Object key) {
5149
}
5250
@Override
5351
public V put(K key, V value) {
54-
return lock.apply(key, () -> value);
52+
return lock.computeIfAbsent(key, k -> value);
5553
}
5654
@Override
5755
public V putIfAbsent(K key, V value) {
58-
return lock.apply(key, () -> value);
56+
return lock.computeIfAbsent(key, k -> value);
5957
}
6058
@Override
6159
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
62-
return lock.apply(key, () -> mappingFunction.apply(key));
60+
return lock.computeIfAbsent(key, mappingFunction::apply);
6361
}
6462
@Override public int size() { throw new UnsupportedOperationException(); }
6563
@Override public boolean isEmpty() { throw new UnsupportedOperationException(); }

src/main/java/module-info.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414

1515
exports net.minecraftforge.eventbus;
1616
exports net.minecraftforge.eventbus.api;
17+
18+
/**
19+
* Internal classes that may change or be removed at any time without notice
20+
*/
21+
exports net.minecraftforge.eventbus.internal to net.minecraftforge.eventbus.test;
22+
1723
provides cpw.mods.modlauncher.serviceapi.ILaunchPluginService with net.minecraftforge.eventbus.service.ModLauncherService;
1824
provides net.minecraftforge.eventbus.IEventBusEngine with net.minecraftforge.eventbus.EventBusEngine;
19-
}
25+
}

src/main/java/net/minecraftforge/eventbus/Cache.java

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/main/java/net/minecraftforge/eventbus/ClassLoaderFactory.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import java.lang.reflect.InvocationTargetException;
88
import java.lang.reflect.Method;
99
import java.lang.reflect.Modifier;
10+
11+
import net.minecraftforge.eventbus.internal.Cache;
1012
import org.objectweb.asm.ClassWriter;
1113
import org.objectweb.asm.MethodVisitor;
1214
import org.objectweb.asm.Type;
@@ -21,7 +23,7 @@ public class ClassLoaderFactory implements IEventListenerFactory {
2123
private static final String HANDLER_DESC = Type.getInternalName(IEventListener.class);
2224
private static final String HANDLER_FUNC_DESC = Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Event.class));
2325
private static final ASMClassLoader LOADER = new ASMClassLoader();
24-
private static final Cache<Method, Class<?>> cache = InternalUtils.cache();
26+
private static final Cache<Method, Class<?>> cache = Cache.create();
2527

2628
@Override
2729
public IEventListener create(Method method, Object target) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, ClassNotFoundException {
@@ -34,14 +36,14 @@ public IEventListener create(Method method, Object target) throws InstantiationE
3436

3537

3638
protected Class<?> createWrapper(Method callback) throws ClassNotFoundException {
37-
return cache.computeIfAbsent(callback, () -> {
39+
return cache.computeIfAbsent(callback, k -> {
3840
var node = new ClassNode();
39-
transformNode(getUniqueName(callback), callback, node);
41+
transformNode(getUniqueName(k), k, node);
4042
return node;
4143
}, ClassLoaderFactory::defineClass);
4244
}
4345

44-
private static final Class<?> defineClass(ClassNode node) {
46+
private static Class<?> defineClass(ClassNode node) {
4547
var cw = new ClassWriter(0);
4648
node.accept(cw);
4749
return LOADER.define(node.name.replace('/', '.'), cw.toByteArray());
@@ -111,7 +113,7 @@ else if (isInterface)
111113
target.visitEnd();
112114
}
113115

114-
private static class ASMClassLoader extends ClassLoader {
116+
private static final class ASMClassLoader extends ClassLoader {
115117
private ASMClassLoader() {
116118
super(null);
117119
}

src/main/java/net/minecraftforge/eventbus/LogMarkers.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import org.apache.logging.log4j.Marker;
88
import org.apache.logging.log4j.MarkerManager;
99

10-
class LogMarkers {
10+
final class LogMarkers {
11+
private LogMarkers() {}
12+
1113
static final Marker EVENTBUS = MarkerManager.getMarker("EVENTBUS");
1214
}

src/main/java/net/minecraftforge/eventbus/ModLauncherFactory.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,16 @@
55
package net.minecraftforge.eventbus;
66

77
import java.lang.reflect.Method;
8-
import java.util.HashMap;
98
import java.util.Optional;
9+
10+
import net.minecraftforge.eventbus.internal.Cache;
1011
import org.objectweb.asm.tree.ClassNode;
1112

1213
import cpw.mods.modlauncher.Launcher;
1314
import cpw.mods.modlauncher.api.IModuleLayerManager;
1415

1516
public class ModLauncherFactory extends ClassLoaderFactory {
16-
private static final Cache<String, Method> PENDING = InternalUtils.cache();
17+
private static final Cache<String, Method> PENDING = Cache.create();
1718
private Optional<ClassLoader> gameClassLoader = null;
1819

1920
@Override
@@ -25,7 +26,7 @@ protected Class<?> createWrapper(Method callback) throws ClassNotFoundException
2526

2627
private void enqueueWrapper(Method callback) {
2728
String name = getUniqueName(callback);
28-
PENDING.computeIfAbsent(name, () -> callback);
29+
PENDING.computeIfAbsent(name, k -> callback);
2930
}
3031

3132
public static boolean hasPendingWrapperClass(final String className) {

src/main/java/net/minecraftforge/eventbus/Names.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
import org.objectweb.asm.tree.MethodNode;
1010

11-
class Names {
11+
final class Names {
12+
private Names() {}
13+
1214
static final String SUBSCRIBE_EVENT = "Lnet/minecraftforge/eventbus/api/SubscribeEvent;";
1315
static final String HAS_RESULT = "Lnet/minecraftforge/eventbus/api/Event$HasResult;";
1416
static final Method HAS_RESULT_M = new Method("hasResult", getMethodDescriptor(BOOLEAN_TYPE));
@@ -26,7 +28,7 @@ class Names {
2628
static final Method INIT_M = new Method("<init>", getMethodDescriptor(VOID_TYPE));
2729
static final Method STATIC_INIT_M = new Method("<clinit>", getMethodDescriptor(VOID_TYPE));
2830

29-
static record Method(String name, String desc) {
31+
record Method(String name, String desc) {
3032
boolean equals(MethodNode node) {
3133
return this.name.equals(node.name) && this.desc.equals(node.desc);
3234
}

src/main/java/net/minecraftforge/eventbus/api/EventListenerHelper.java

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,21 @@
44
*/
55
package net.minecraftforge.eventbus.api;
66

7-
import net.minecraftforge.eventbus.InternalUtils;
87
import net.minecraftforge.eventbus.ListenerList;
98
import net.minecraftforge.eventbus.api.Event.HasResult;
9+
import net.minecraftforge.eventbus.internal.Cache;
1010

1111
import java.lang.annotation.Annotation;
1212
import java.lang.reflect.Constructor;
1313
import java.lang.reflect.InvocationTargetException;
1414
import java.lang.reflect.Modifier;
15-
import java.util.function.BiFunction;
16-
import java.util.function.Supplier;
1715

1816
public class EventListenerHelper {
19-
private static final BiFunction<Class<?>, Supplier<ListenerList>, ListenerList> listeners = InternalUtils.cachePublic();
17+
private static final Cache<Class<?>, ListenerList> listeners = Cache.create();
2018
private static final ListenerList EVENTS_LIST = new ListenerList();
21-
private static final BiFunction<Class<?>, Supplier<Boolean>, Boolean> cancelable = InternalUtils.cachePublic();
22-
private static final BiFunction<Class<?>, Supplier<Boolean>, Boolean> hasResult = InternalUtils.cachePublic();
19+
private static final Cache<Class<?>, Boolean> cancelable = Cache.create();
20+
private static final Cache<Class<?>, Boolean> hasResult = Cache.create();
21+
2322
/**
2423
* Returns a {@link ListenerList} object that contains all listeners
2524
* that are registered to this event class.
@@ -35,7 +34,13 @@ public static ListenerList getListenerList(Class<?> eventClass) {
3534

3635
static ListenerList getListenerListInternal(Class<?> eventClass, boolean fromInstanceCall) {
3736
if (eventClass == Event.class) return EVENTS_LIST; // Small optimization, bypasses all the locks/maps.
38-
return listeners.apply(eventClass, () -> computeListenerList(eventClass, fromInstanceCall));
37+
38+
// Attempt to get the ListenerList directly from the cache first. This avoids an allocating lambda on cache hit
39+
var ret = listeners.get(eventClass);
40+
if (ret != null) return ret;
41+
42+
// Cache miss, check again (for thread-safety) and compute the ListenerList if still absent
43+
return listeners.computeIfAbsent(eventClass, k -> computeListenerList(k, fromInstanceCall));
3944
}
4045

4146
private static ListenerList computeListenerList(Class<?> eventClass, boolean fromInstanceCall) {
@@ -66,14 +71,17 @@ static boolean hasResult(Class<?> eventClass) {
6671
return hasAnnotation(eventClass, HasResult.class, hasResult);
6772
}
6873

69-
private static boolean hasAnnotation(Class<?> eventClass, Class<? extends Annotation> annotation, BiFunction<Class<?>, Supplier<Boolean>, Boolean> cache) {
74+
private static boolean hasAnnotation(Class<?> eventClass, Class<? extends Annotation> annotation, Cache<Class<?>, Boolean> cache) {
7075
if (eventClass == Event.class || eventClass == Object.class)
7176
return false;
7277

73-
return cache.apply(eventClass, () -> {
74-
if (eventClass.isAnnotationPresent(annotation))
78+
var ret = cache.get(eventClass);
79+
if (ret != null) return ret;
80+
81+
return cache.computeIfAbsent(eventClass, k -> {
82+
if (k.isAnnotationPresent(annotation))
7583
return true;
76-
var parent = eventClass.getSuperclass();
84+
var parent = k.getSuperclass();
7785
return parent != null && hasAnnotation(parent, annotation, cache);
7886
});
7987
}

src/main/java/net/minecraftforge/eventbus/InternalUtils.java renamed to src/main/java/net/minecraftforge/eventbus/internal/Cache.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,24 @@
22
* Copyright (c) Forge Development LLC
33
* SPDX-License-Identifier: LGPL-2.1-only
44
*/
5-
package net.minecraftforge.eventbus;
5+
package net.minecraftforge.eventbus.internal;
66

7-
import java.util.HashMap;
8-
import java.util.function.BiFunction;
9-
import java.util.function.Supplier;
107
import org.jetbrains.annotations.ApiStatus;
118

12-
/* Internal Utility class, only public so that other classes in the EventBus project can access things.
13-
* MODDERS SHOULD NEVER TOUCH THIS. And I reserve the right to do any breaking changes I want to this class.
14-
*/
9+
import java.util.HashMap;
10+
import java.util.function.Function;
11+
1512
@ApiStatus.Internal
16-
public class InternalUtils {
17-
public static <K,V> BiFunction<K, Supplier<V>, V> cachePublic() {
18-
return cache();
13+
public interface Cache<K, V> {
14+
V get(K key);
15+
16+
default V computeIfAbsent(K key, Function<K, V> factory) {
17+
return computeIfAbsent(key, factory, Function.identity());
1918
}
2019

21-
static <K, V> Cache<K, V> cache() {
20+
<I> V computeIfAbsent(K key, Function<K, I> factory, Function<I, V> finalizer);
21+
22+
static <K, V> Cache<K, V> create() {
2223
var type = System.getProperty("eb.cache_type");
2324
if (type == null || "concurrent".equals(type))
2425
return new CacheConcurrent<>();

src/main/java/net/minecraftforge/eventbus/CacheConcurrent.java renamed to src/main/java/net/minecraftforge/eventbus/internal/CacheConcurrent.java

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,19 @@
22
* Copyright (c) Forge Development LLC
33
* SPDX-License-Identifier: LGPL-2.1-only
44
*/
5-
package net.minecraftforge.eventbus;
5+
package net.minecraftforge.eventbus.internal;
66

7-
import java.util.Map;
87
import java.util.concurrent.ConcurrentHashMap;
98
import java.util.function.Function;
10-
import java.util.function.Supplier;
119

1210
/*
1311
* An implementation of the Cache class that uses a ConcurrentHashMap for the backing map.
1412
* This allows us to use no locks when calling the get method.
1513
* However, it has re-entrant issues when writing. So we guard that using a synchronized block
1614
*/
17-
class CacheConcurrent<K,V> implements Cache<K, V> {
18-
private Object lock = new Object();
19-
private final Map<K, V> map;
20-
15+
record CacheConcurrent<K, V>(ConcurrentHashMap<K, V> map, Object lock) implements Cache<K, V> {
2116
CacheConcurrent() {
22-
this.map = new ConcurrentHashMap<>(32);
17+
this(new ConcurrentHashMap<>(32), new Object());
2318
}
2419

2520
@Override
@@ -28,7 +23,7 @@ public V get(K key) {
2823
}
2924

3025
@Override
31-
public <I> V computeIfAbsent(K key, Supplier<I> factory, Function<I, V> finalizer) {
26+
public <I> V computeIfAbsent(K key, Function<K, I> factory, Function<I, V> finalizer) {
3227
// This is a put once map, so lets try checking if the map has this value.
3328
// Should be thread safe to read without lock as any writes will be guarded
3429
var ret = get(key);
@@ -39,7 +34,7 @@ public <I> V computeIfAbsent(K key, Supplier<I> factory, Function<I, V> finalize
3934

4035
// Let's pre-compute our new value. This could take a while, as well as recursively call this
4136
// function. as such, we need to make sure we don't hold a lock when we do this
42-
var intermediate = factory.get();
37+
var intermediate = factory.apply(key);
4338

4439
// We are actually gunna modify the map now, so prevent other threads form doing so
4540
synchronized (lock) {

0 commit comments

Comments
 (0)