Skip to content

Commit 8fdf3be

Browse files
committed
Separate per-store context storage from boxed store-ids that forward to ObjectStoreDispatch
Selects the context-store implementation at startup and emits direct references to its concrete type so the JIT can optimize store access. Static ObjectStoreDispatch which uses just a store-id is moved into BoxedContextStore, while FieldBackedContextStore retains the per-store weak-map fallback. Updates instrumentation rewriting, OpenTelemetry mapping, and native-image initialization to use the selected implementation. Adds concurrent allocation tests and JMH benchmarks for store operations and a variety of workloads.
1 parent 1c373d5 commit 8fdf3be

37 files changed

Lines changed: 2246 additions & 100 deletions

File tree

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package datadog.trace.bootstrap;
2+
3+
import datadog.instrument.fieldinject.ObjectStoreDispatch;
4+
import java.util.function.Function;
5+
6+
/** Boxes a store-id and dispatches context operations through {@link ObjectStoreDispatch}. */
7+
public final class BoxedContextStore implements ContextStore<Object, Object> {
8+
final int storeId;
9+
10+
BoxedContextStore(final int storeId) {
11+
this.storeId = storeId;
12+
}
13+
14+
@Override
15+
public Object get(final Object key) {
16+
return ObjectStoreDispatch.get(key, storeId);
17+
}
18+
19+
@Override
20+
public void put(final Object key, final Object context) {
21+
ObjectStoreDispatch.put(key, storeId, context);
22+
}
23+
24+
@Override
25+
public Object getOrPut(final Object key, final Object context) {
26+
return ObjectStoreDispatch.getOrPut(key, storeId, context);
27+
}
28+
29+
@Override
30+
public Object getOrCompute(
31+
final Object key, final Function<? super Object, Object> contextFactory) {
32+
return ObjectStoreDispatch.getOrCompute(key, storeId, contextFactory);
33+
}
34+
35+
@Override
36+
public Object remove(final Object key) {
37+
return ObjectStoreDispatch.remove(key, storeId);
38+
}
39+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package datadog.trace.bootstrap;
2+
3+
import java.util.Arrays;
4+
import java.util.concurrent.ConcurrentHashMap;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
/**
9+
* Intentional copy of FieldBackedContextStores but for {@link BoxedContextStore}.
10+
*
11+
* <p>Only one is loaded at runtime; the explicit field type helps the JIT optimize.
12+
*/
13+
public final class BoxedContextStores {
14+
15+
private static final Logger log = LoggerFactory.getLogger(BoxedContextStores.class);
16+
17+
// these fields will be accessed directly from field-injected instrumentation
18+
public static final BoxedContextStore contextStore0 = new BoxedContextStore(0);
19+
public static final BoxedContextStore contextStore1 = new BoxedContextStore(1);
20+
public static final BoxedContextStore contextStore2 = new BoxedContextStore(2);
21+
public static final BoxedContextStore contextStore3 = new BoxedContextStore(3);
22+
public static final BoxedContextStore contextStore4 = new BoxedContextStore(4);
23+
public static final BoxedContextStore contextStore5 = new BoxedContextStore(5);
24+
public static final BoxedContextStore contextStore6 = new BoxedContextStore(6);
25+
public static final BoxedContextStore contextStore7 = new BoxedContextStore(7);
26+
public static final BoxedContextStore contextStore8 = new BoxedContextStore(8);
27+
public static final BoxedContextStore contextStore9 = new BoxedContextStore(9);
28+
public static final BoxedContextStore contextStore10 = new BoxedContextStore(10);
29+
public static final BoxedContextStore contextStore11 = new BoxedContextStore(11);
30+
public static final BoxedContextStore contextStore12 = new BoxedContextStore(12);
31+
public static final BoxedContextStore contextStore13 = new BoxedContextStore(13);
32+
public static final BoxedContextStore contextStore14 = new BoxedContextStore(14);
33+
public static final BoxedContextStore contextStore15 = new BoxedContextStore(15);
34+
public static final BoxedContextStore contextStore16 = new BoxedContextStore(16);
35+
public static final BoxedContextStore contextStore17 = new BoxedContextStore(17);
36+
public static final BoxedContextStore contextStore18 = new BoxedContextStore(18);
37+
public static final BoxedContextStore contextStore19 = new BoxedContextStore(19);
38+
public static final BoxedContextStore contextStore20 = new BoxedContextStore(20);
39+
public static final BoxedContextStore contextStore21 = new BoxedContextStore(21);
40+
public static final BoxedContextStore contextStore22 = new BoxedContextStore(22);
41+
public static final BoxedContextStore contextStore23 = new BoxedContextStore(23);
42+
public static final BoxedContextStore contextStore24 = new BoxedContextStore(24);
43+
public static final BoxedContextStore contextStore25 = new BoxedContextStore(25);
44+
public static final BoxedContextStore contextStore26 = new BoxedContextStore(26);
45+
public static final BoxedContextStore contextStore27 = new BoxedContextStore(27);
46+
public static final BoxedContextStore contextStore28 = new BoxedContextStore(28);
47+
public static final BoxedContextStore contextStore29 = new BoxedContextStore(29);
48+
public static final BoxedContextStore contextStore30 = new BoxedContextStore(30);
49+
public static final BoxedContextStore contextStore31 = new BoxedContextStore(31);
50+
51+
// keep track of all allocated stores so far
52+
private static volatile BoxedContextStore[] stores = {
53+
contextStore0,
54+
contextStore1,
55+
contextStore2,
56+
contextStore3,
57+
contextStore4,
58+
contextStore5,
59+
contextStore6,
60+
contextStore7,
61+
contextStore8,
62+
contextStore9,
63+
contextStore10,
64+
contextStore11,
65+
contextStore12,
66+
contextStore13,
67+
contextStore14,
68+
contextStore15,
69+
contextStore16,
70+
contextStore17,
71+
contextStore18,
72+
contextStore19,
73+
contextStore20,
74+
contextStore21,
75+
contextStore22,
76+
contextStore23,
77+
contextStore24,
78+
contextStore25,
79+
contextStore26,
80+
contextStore27,
81+
contextStore28,
82+
contextStore29,
83+
contextStore30,
84+
contextStore31
85+
};
86+
87+
private static final ConcurrentHashMap<String, BoxedContextStore> storesByName =
88+
new ConcurrentHashMap<>();
89+
private static final Object allocationLock = new Object();
90+
91+
private static int nextStoreId;
92+
93+
public static int getContextStoreId(String keyClassName, String contextClassName) {
94+
String storeName = keyClassName + ';' + contextClassName;
95+
BoxedContextStore store = storesByName.get(storeName);
96+
if (store == null) {
97+
synchronized (allocationLock) {
98+
// speculatively create the next store in the sequence and attempt to map this name to it;
99+
// if another thread has mapped this name then the store will be kept for the next mapping
100+
BoxedContextStore existing =
101+
storesByName.putIfAbsent(storeName, store = allocateStore(nextStoreId));
102+
if (existing != null) {
103+
return existing.storeId;
104+
}
105+
nextStoreId++;
106+
}
107+
log.debug(
108+
"Allocated ContextStore #{} - instrumentation.target.context={}->{}",
109+
store.storeId,
110+
keyClassName,
111+
contextClassName);
112+
}
113+
return store.storeId;
114+
}
115+
116+
public static BoxedContextStore getContextStore(final int storeId) {
117+
return stores[storeId]; // guaranteed to be populated by getContextStoreId
118+
}
119+
120+
// this method should only be called while holding the allocation lock
121+
private static BoxedContextStore allocateStore(int storeId) {
122+
BoxedContextStore[] snapshot = stores;
123+
if (storeId >= snapshot.length) {
124+
stores = snapshot = Arrays.copyOf(snapshot, storeId + 16);
125+
}
126+
// check for pre-allocated / speculatively allocated stores
127+
BoxedContextStore store = snapshot[storeId];
128+
if (store == null) {
129+
snapshot[storeId] = store = new BoxedContextStore(storeId);
130+
}
131+
return store;
132+
}
133+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package datadog.trace.bootstrap;
2+
3+
import datadog.trace.api.InstrumenterConfig;
4+
5+
/** Allocates {@link ContextStore}s. */
6+
public final class ContextStores {
7+
8+
// fast lookup available for fixed number of stores
9+
public static final int FAST_STORE_ID_LIMIT = 32;
10+
11+
private static final boolean MAP_PER_STORE =
12+
InstrumenterConfig.get().isRuntimeContextMapPerStore();
13+
14+
public static final String STORES_DESCRIPTOR =
15+
MAP_PER_STORE
16+
? "Ldatadog/trace/bootstrap/FieldBackedContextStores;"
17+
: "Ldatadog/trace/bootstrap/BoxedContextStores;";
18+
19+
public static final String STORE_DESCRIPTOR =
20+
MAP_PER_STORE
21+
? "Ldatadog/trace/bootstrap/FieldBackedContextStore;"
22+
: "Ldatadog/trace/bootstrap/BoxedContextStore;";
23+
24+
public static int getContextStoreId(String keyClassName, String contextClassName) {
25+
if (MAP_PER_STORE) {
26+
return FieldBackedContextStores.getContextStoreId(keyClassName, contextClassName);
27+
} else {
28+
return BoxedContextStores.getContextStoreId(keyClassName, contextClassName);
29+
}
30+
}
31+
32+
public static ContextStore<?, ?> getContextStore(final int storeId) {
33+
if (MAP_PER_STORE) {
34+
return FieldBackedContextStores.getContextStore(storeId);
35+
} else {
36+
return BoxedContextStores.getContextStore(storeId);
37+
}
38+
}
39+
}

‎dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/FieldBackedContextStore.java‎

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
package datadog.trace.bootstrap;
22

33
import datadog.instrument.fieldinject.KeyWithValue;
4-
import datadog.instrument.fieldinject.ObjectStoreDispatch;
5-
import datadog.trace.api.InstrumenterConfig;
64
import java.util.function.Function;
75

86
/**
97
* {@link ContextStore} that attempts to store context in its keys by using bytecode-injected
108
* fields. Delegates to a lazy {@link WeakMap} for keys that don't have a field for this store.
119
*/
1210
public final class FieldBackedContextStore implements ContextStore<Object, Object> {
13-
private static final boolean MAP_PER_STORE =
14-
InstrumenterConfig.get().isRuntimeContextMapPerStore();
15-
1611
final int storeId;
1712

1813
FieldBackedContextStore(final int storeId) {
@@ -23,21 +18,17 @@ public final class FieldBackedContextStore implements ContextStore<Object, Objec
2318
public Object get(final Object key) {
2419
if (key instanceof KeyWithValue) {
2520
return ((KeyWithValue) key).$get$__dd_instrument$(storeId);
26-
} else if (MAP_PER_STORE) {
27-
return weakStore().get(key);
2821
} else {
29-
return ObjectStoreDispatch.get(key, storeId);
22+
return weakStore().get(key);
3023
}
3124
}
3225

3326
@Override
3427
public void put(final Object key, final Object context) {
3528
if (key instanceof KeyWithValue) {
3629
((KeyWithValue) key).$put$__dd_instrument$(storeId, context);
37-
} else if (MAP_PER_STORE) {
38-
weakStore().put(key, context);
3930
} else {
40-
ObjectStoreDispatch.put(key, storeId, context);
31+
weakStore().put(key, context);
4132
}
4233
}
4334

@@ -56,15 +47,14 @@ public Object getOrPut(final Object key, final Object context) {
5647
}
5748
}
5849
return existingContext;
59-
} else if (MAP_PER_STORE) {
60-
return weakStore().getOrPut(key, context);
6150
} else {
62-
return ObjectStoreDispatch.getOrPut(key, storeId, context);
51+
return weakStore().getOrPut(key, context);
6352
}
6453
}
6554

6655
@Override
67-
public Object getOrCompute(Object key, Function<? super Object, Object> contextFactory) {
56+
public Object getOrCompute(
57+
final Object key, final Function<? super Object, Object> contextFactory) {
6858
if (key instanceof KeyWithValue) {
6959
final KeyWithValue accessor = (KeyWithValue) key;
7060
Object existingContext = accessor.$get$__dd_instrument$(storeId);
@@ -78,15 +68,13 @@ public Object getOrCompute(Object key, Function<? super Object, Object> contextF
7868
}
7969
}
8070
return existingContext;
81-
} else if (MAP_PER_STORE) {
82-
return weakStore().getOrCompute(key, contextFactory);
8371
} else {
84-
return ObjectStoreDispatch.getOrCompute(key, storeId, contextFactory);
72+
return weakStore().getOrCompute(key, contextFactory);
8573
}
8674
}
8775

8876
@Override
89-
public Object remove(Object key) {
77+
public Object remove(final Object key) {
9078
if (key instanceof KeyWithValue) {
9179
final KeyWithValue accessor = (KeyWithValue) key;
9280
Object existingContext = accessor.$get$__dd_instrument$(storeId);
@@ -99,10 +87,8 @@ public Object remove(Object key) {
9987
}
10088
}
10189
return existingContext;
102-
} else if (MAP_PER_STORE) {
103-
return weakStore().remove(key);
10490
} else {
105-
return ObjectStoreDispatch.remove(key, storeId);
91+
return weakStore().remove(key);
10692
}
10793
}
10894

‎dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/FieldBackedContextStores.java‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ public final class FieldBackedContextStores {
1010

1111
private static final Logger log = LoggerFactory.getLogger(FieldBackedContextStores.class);
1212

13-
// provide fast lookup for a fixed number of stores
14-
public static final int FAST_STORE_ID_LIMIT = 32;
15-
1613
// these fields will be accessed directly from field-injected instrumentation
1714
public static final FieldBackedContextStore contextStore0 = new FieldBackedContextStore(0);
1815
public static final FieldBackedContextStore contextStore1 = new FieldBackedContextStore(1);

‎dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/FieldBackedContextStoresTest.groovy‎

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

0 commit comments

Comments
 (0)