Skip to content

Commit 15180ec

Browse files
authored
Merge pull request #25835 from Sanne/ArcOpt
Micro allocations optimisation in ArC
2 parents 7b79305 + 95f70e8 commit 15180ec

File tree

3 files changed

+169
-17
lines changed

3 files changed

+169
-17
lines changed

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AbstractInvocationContext.java

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
import java.lang.reflect.Method;
77
import java.util.ArrayList;
88
import java.util.Arrays;
9-
import java.util.HashMap;
109
import java.util.List;
1110
import java.util.Map;
1211
import java.util.Objects;
1312
import java.util.Set;
14-
import java.util.function.Supplier;
1513

16-
abstract class AbstractInvocationContext implements ArcInvocationContext, Supplier<Map<String, Object>> {
14+
abstract class AbstractInvocationContext implements ArcInvocationContext {
1715

1816
private static final Object[] EMPTY_PARAMS = new Object[0];
1917

@@ -23,25 +21,24 @@ abstract class AbstractInvocationContext implements ArcInvocationContext, Suppli
2321
protected final List<InterceptorInvocation> chain;
2422
protected Object target;
2523
protected Object[] parameters;
26-
// The map is initialized lazily but we need to use a holder so that all interceptors in the chain can access the same data
27-
protected LazyValue<Map<String, Object>> contextData;
24+
protected ContextDataMap contextData;
2825

2926
protected AbstractInvocationContext(Object target, Method method,
3027
Constructor<?> constructor,
31-
Object[] parameters, LazyValue<Map<String, Object>> contextData,
28+
Object[] parameters, ContextDataMap contextData,
3229
Set<Annotation> interceptorBindings, List<InterceptorInvocation> chain) {
3330
this.target = target;
3431
this.method = method;
3532
this.constructor = constructor;
3633
this.parameters = parameters != null ? parameters : EMPTY_PARAMS;
37-
this.contextData = contextData != null ? contextData : new LazyValue<>(this);
34+
this.contextData = contextData != null ? contextData : new ContextDataMap(interceptorBindings);
3835
this.interceptorBindings = interceptorBindings;
3936
this.chain = chain;
4037
}
4138

4239
@Override
4340
public Map<String, Object> getContextData() {
44-
return contextData.get();
41+
return contextData;
4542
}
4643

4744
@Override
@@ -125,11 +122,4 @@ public Constructor<?> getConstructor() {
125122
return constructor;
126123
}
127124

128-
@Override
129-
public Map<String, Object> get() {
130-
Map<String, Object> result = new HashMap<String, Object>();
131-
result.put(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings);
132-
return result;
133-
}
134-
135125
}

independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/AroundInvokeInvocationContext.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import java.lang.reflect.InvocationTargetException;
55
import java.lang.reflect.Method;
66
import java.util.List;
7-
import java.util.Map;
87
import java.util.Set;
98
import java.util.function.Function;
109
import javax.interceptor.InvocationContext;
@@ -26,7 +25,7 @@ class AroundInvokeInvocationContext extends AbstractInvocationContext {
2625
private final Function<InvocationContext, Object> aroundInvokeForward;
2726

2827
AroundInvokeInvocationContext(Object target, Method method, Object[] parameters,
29-
LazyValue<Map<String, Object>> contextData, Set<Annotation> interceptorBindings, int position,
28+
ContextDataMap contextData, Set<Annotation> interceptorBindings, int position,
3029
List<InterceptorInvocation> chain, Function<InvocationContext, Object> aroundInvokeForward) {
3130
super(target, method, null, parameters, contextData, interceptorBindings, chain);
3231
this.position = position;
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
package io.quarkus.arc.impl;
2+
3+
import io.quarkus.arc.ArcInvocationContext;
4+
import java.lang.annotation.Annotation;
5+
import java.util.AbstractMap;
6+
import java.util.ArrayList;
7+
import java.util.Collection;
8+
import java.util.Collections;
9+
import java.util.HashMap;
10+
import java.util.HashSet;
11+
import java.util.Map;
12+
import java.util.Objects;
13+
import java.util.Set;
14+
import javax.interceptor.InvocationContext;
15+
16+
/**
17+
* This Map implementation is specifically optimised to implement
18+
* {@link InvocationContext#getContextData()}.
19+
* We try to leverage the fact that in most use cases, while there is a high
20+
* chance for integration points to need to read the content of this map,
21+
* write operations are less likely. So we only allocate an actual underlying
22+
* HashMap on a write operation.
23+
* In addition, an {@link ArcInvocationContext} always allows to return the set of
24+
* interceptor bindings via {@link ArcInvocationContext#KEY_INTERCEPTOR_BINDINGS};
25+
* this is implemented while avoiding a write operation on the underlying Map.
26+
* While one goal is efficiency, another goal is safety: while the CDI specification
27+
* allows any interceptor to make changes to this Map, we specifically disallow
28+
* 3rd party code to make changes to the interceptor bindings of the platform.
29+
*/
30+
final class ContextDataMap implements Map<String, Object> {
31+
32+
private final Set<Annotation> interceptorBindings;
33+
private HashMap<String, Object> delegate; //important to lazily initialize this
34+
35+
ContextDataMap(Set<Annotation> interceptorBindings) {
36+
this.interceptorBindings = Objects.requireNonNull(interceptorBindings);
37+
}
38+
39+
private Map<String, Object> getDelegateForRead() {
40+
if (delegate == null) {
41+
return Collections.EMPTY_MAP;
42+
} else {
43+
return delegate;
44+
}
45+
}
46+
47+
private Map<String, Object> getDelegateForWrite(final int sizeHint) {
48+
if (delegate == null) {
49+
this.delegate = new HashMap<>(sizeHint, 1.0f);
50+
}
51+
return delegate;
52+
}
53+
54+
// ** Implement methods from the Map interface **
55+
56+
@Override
57+
public void clear() {
58+
throw new UnsupportedOperationException("Not allowed to clear the context data map");
59+
}
60+
61+
@Override
62+
public boolean containsKey(Object key) {
63+
if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) {
64+
return true;
65+
}
66+
return getDelegateForRead().containsKey(key);
67+
}
68+
69+
@Override
70+
public boolean containsValue(Object value) {
71+
if (interceptorBindings.equals(value)) {
72+
return true;
73+
}
74+
return getDelegateForRead().containsValue(value);
75+
}
76+
77+
@Override
78+
public Set<Map.Entry<String, Object>> entrySet() {
79+
final AbstractMap.SimpleImmutableEntry<String, Object> firstEntry = new AbstractMap.SimpleImmutableEntry<>(
80+
ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings);
81+
if (delegate == null) {
82+
return Collections.singleton(firstEntry);
83+
} else {
84+
HashSet entries = new HashSet(delegate.size() + 1);
85+
entries.addAll(delegate.entrySet());
86+
entries.add(firstEntry);
87+
return entries;
88+
}
89+
}
90+
91+
@Override
92+
public Object get(Object key) {
93+
if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) {
94+
return interceptorBindings;
95+
}
96+
return getDelegateForRead().get(key);
97+
}
98+
99+
@Override
100+
public boolean isEmpty() {
101+
return false;
102+
}
103+
104+
@Override
105+
public Set<String> keySet() {
106+
if (delegate == null) {
107+
return Collections.singleton(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS);
108+
} else {
109+
HashSet set = new HashSet(delegate.size() + 1);
110+
set.addAll(delegate.keySet());
111+
set.add(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS);
112+
return set;
113+
}
114+
}
115+
116+
@Override
117+
public void putAll(Map m) {
118+
if (m.containsKey(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS)) {
119+
throw new IllegalArgumentException(
120+
"Not allowed to put key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS + "' in the context data map");
121+
}
122+
getDelegateForWrite(m.size()).putAll(m);
123+
}
124+
125+
@Override
126+
public Object remove(Object key) {
127+
if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) {
128+
throw new IllegalArgumentException("Not allowed to remove key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS
129+
+ "' from the context data map");
130+
} else if (delegate == null) {
131+
return null;
132+
} else {
133+
return delegate.remove(key);
134+
}
135+
}
136+
137+
@Override
138+
public int size() {
139+
return getDelegateForRead().size() + 1;
140+
}
141+
142+
@Override
143+
public Collection values() {
144+
if (delegate == null) {
145+
return Collections.singleton(interceptorBindings);
146+
} else {
147+
ArrayList list = new ArrayList(delegate.size() + 1);
148+
list.addAll(delegate.values());
149+
list.add(interceptorBindings);
150+
return list;
151+
}
152+
}
153+
154+
@Override
155+
public Object put(String key, Object value) {
156+
if (ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS.equals(key)) {
157+
throw new IllegalArgumentException(
158+
"Not allowed to put key '" + ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS + "' in the context data map");
159+
}
160+
return getDelegateForWrite(1).put(key, value);
161+
}
162+
163+
}

0 commit comments

Comments
 (0)