Skip to content

Commit 6311410

Browse files
committed
[GR-53406] Highly concurrent execution with shared engines contend on TransitionMap.
PullRequest: graal/17487
2 parents 2b4b3c5 + b4be9be commit 6311410

File tree

13 files changed

+945
-214
lines changed

13 files changed

+945
-214
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.api.benchmark;
42+
43+
import java.util.stream.IntStream;
44+
45+
import org.graalvm.polyglot.Context;
46+
import org.graalvm.polyglot.Engine;
47+
import org.openjdk.jmh.annotations.Benchmark;
48+
import org.openjdk.jmh.annotations.Scope;
49+
import org.openjdk.jmh.annotations.Setup;
50+
import org.openjdk.jmh.annotations.State;
51+
import org.openjdk.jmh.annotations.TearDown;
52+
import org.openjdk.jmh.annotations.Threads;
53+
import org.openjdk.jmh.annotations.Warmup;
54+
55+
import com.oracle.truffle.api.object.DynamicObject;
56+
import com.oracle.truffle.api.object.DynamicObjectLibrary;
57+
import com.oracle.truffle.api.object.Shape;
58+
59+
@Warmup(iterations = 10, time = 1)
60+
@SuppressWarnings("deprecation")
61+
public class DynamicObjectBenchmark extends TruffleBenchmark {
62+
63+
static final String TEST_LANGUAGE = "benchmark-test-language";
64+
private static final int PROPERTY_KEYS_PER_ITERATION = 1000;
65+
66+
private static final class MyDynamicObject extends DynamicObject {
67+
private MyDynamicObject(Shape shape) {
68+
super(shape);
69+
}
70+
}
71+
72+
@State(Scope.Benchmark)
73+
public static class SharedEngineState {
74+
final Engine engine = Engine.newBuilder().allowExperimentalOptions(true).option("engine.Compilation", "false").build();
75+
final Shape rootShape = Shape.newBuilder().build();
76+
final String[] propertyKeys = IntStream.range(0, PROPERTY_KEYS_PER_ITERATION).mapToObj(i -> "testKey" + i).toArray(String[]::new);
77+
final Shape[] expectedShapes = new Shape[PROPERTY_KEYS_PER_ITERATION];
78+
79+
@TearDown
80+
public void tearDown() {
81+
engine.close();
82+
}
83+
84+
private void assertSameShape(int i, Shape actualShape) {
85+
Shape expectedShape = expectedShapes[i];
86+
if (expectedShape == null) {
87+
expectedShapes[i] = actualShape;
88+
} else if (expectedShape != actualShape) {
89+
throw new AssertionError("Expected shape: " + expectedShape + " but was: " + actualShape);
90+
}
91+
}
92+
}
93+
94+
@State(Scope.Thread)
95+
public static class PerThreadContextState {
96+
Context context;
97+
98+
@Setup
99+
public void setup(SharedEngineState shared) {
100+
context = Context.newBuilder(TEST_LANGUAGE).engine(shared.engine).build();
101+
}
102+
103+
@TearDown
104+
public void tearDown() {
105+
context.close();
106+
}
107+
}
108+
109+
/**
110+
* Benchmark that stresses the shape transition cache with multi-threaded lookups.
111+
*/
112+
@Benchmark
113+
@Threads(8)
114+
public void shapeTransitionMapContended(SharedEngineState shared, PerThreadContextState perThread) {
115+
perThread.context.enter();
116+
for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) {
117+
DynamicObject object = new MyDynamicObject(shared.rootShape);
118+
DynamicObjectLibrary.getUncached().put(object, shared.propertyKeys[i], "testValue");
119+
shared.assertSameShape(i, object.getShape());
120+
}
121+
perThread.context.leave();
122+
}
123+
124+
/**
125+
* Benchmark that stresses the shape transition cache with single-threaded lookups.
126+
*/
127+
@Benchmark
128+
@Threads(1)
129+
public void shapeTransitionMapUncontended(SharedEngineState shared, PerThreadContextState perThread) {
130+
perThread.context.enter();
131+
for (int i = 0; i < PROPERTY_KEYS_PER_ITERATION; i++) {
132+
DynamicObject object = new MyDynamicObject(shared.rootShape);
133+
DynamicObjectLibrary.getUncached().put(object, shared.propertyKeys[i], "testValue");
134+
shared.assertSameShape(i, object.getShape());
135+
}
136+
perThread.context.leave();
137+
}
138+
}

truffle/src/com.oracle.truffle.object/src/com/oracle/truffle/object/ConsListPropertyMap.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ public Property getValue() {
339339
}
340340

341341
public Property setValue(Property value) {
342-
throw unmodifiableException();
342+
throw ImmutableMap.unmodifiableException();
343343
}
344344
}
345345

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* The Universal Permissive License (UPL), Version 1.0
6+
*
7+
* Subject to the condition set forth below, permission is hereby granted to any
8+
* person obtaining a copy of this software, associated documentation and/or
9+
* data (collectively the "Software"), free of charge and under any and all
10+
* copyright rights in the Software, and any and all patent rights owned or
11+
* freely licensable by each licensor hereunder covering either (i) the
12+
* unmodified Software as contributed to or provided by such licensor, or (ii)
13+
* the Larger Works (as defined below), to deal in both
14+
*
15+
* (a) the Software, and
16+
*
17+
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
18+
* one is included with the Software each a "Larger Work" to which the Software
19+
* is contributed by such licensors),
20+
*
21+
* without restriction, including without limitation the rights to copy, create
22+
* derivative works of, display, perform, and distribute the Software and make,
23+
* use, sell, offer for sale, import, export, have made, and have sold the
24+
* Software and the Larger Work(s), and to sublicense the foregoing rights on
25+
* either these or other terms.
26+
*
27+
* This license is subject to the following condition:
28+
*
29+
* The above copyright notice and either this complete permission notice or at a
30+
* minimum a reference to the UPL must be included in all copies or substantial
31+
* portions of the Software.
32+
*
33+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
36+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
37+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
38+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
39+
* SOFTWARE.
40+
*/
41+
package com.oracle.truffle.object;
42+
43+
import java.util.Objects;
44+
import java.util.function.BiConsumer;
45+
46+
import org.graalvm.collections.EconomicMap;
47+
import org.graalvm.collections.Equivalence;
48+
import org.graalvm.collections.MapCursor;
49+
50+
/**
51+
* A synchronized hash map with weakly referenced values. Cleared value references are expunged only
52+
* when the map is mutated. Keys may be strongly or weakly referenced.
53+
*/
54+
final class EconomicTransitionMap<K, V> extends TransitionMap<K, V> {
55+
56+
/** Key is either {@code K} or {@code WeakKey<K>}. */
57+
private final EconomicMap<Object, StrongKeyWeakValueEntry<Object, V>> map;
58+
59+
private static final Equivalence WEAK_KEY_EQUIVALENCE = new WeakKeyEquivalence();
60+
61+
EconomicTransitionMap() {
62+
this.map = EconomicMap.create(WEAK_KEY_EQUIVALENCE);
63+
}
64+
65+
private V getValue(StrongKeyWeakValueEntry<? super K, V> entry) {
66+
return entry == null ? null : entry.get();
67+
}
68+
69+
@Override
70+
@SuppressWarnings("unchecked")
71+
public V get(Object key) {
72+
synchronized (queue) {
73+
return getValue(map.get(key));
74+
}
75+
}
76+
77+
@Override
78+
protected V putAnyKey(Object key, V value) {
79+
synchronized (queue) {
80+
expungeStaleEntries();
81+
return getValue(map.put(key, new StrongKeyWeakValueEntry<>(key, value, queue)));
82+
}
83+
}
84+
85+
@Override
86+
protected V putAnyKeyIfAbsent(Object key, V value) {
87+
synchronized (queue) {
88+
expungeStaleEntries();
89+
/*
90+
* Note: We need to also consider stale weak entries as absent, so we cannot use the
91+
* map's own putIfAbsent here. The reference may have not been enqueued yet either.
92+
*/
93+
var prevValue = getValue(map.get(key));
94+
if (prevValue != null) {
95+
return prevValue;
96+
} else {
97+
map.put(key, new StrongKeyWeakValueEntry<>(key, value, queue));
98+
return null;
99+
}
100+
}
101+
}
102+
103+
@Override
104+
public V remove(Object key) {
105+
synchronized (queue) {
106+
expungeStaleEntries();
107+
return getValue(map.removeKey(key));
108+
}
109+
}
110+
111+
@Override
112+
protected void expungeStaleEntry(StrongKeyWeakValueEntry<Object, V> entry) {
113+
if (map.get(entry.getKey()) == entry) {
114+
map.removeKey(entry.getKey());
115+
}
116+
}
117+
118+
@Override
119+
public void forEach(BiConsumer<? super K, ? super V> consumer) {
120+
synchronized (queue) {
121+
MapCursor<Object, StrongKeyWeakValueEntry<Object, V>> cursor = map.getEntries();
122+
while (cursor.advance()) {
123+
V value = cursor.getValue().get();
124+
if (value != null) {
125+
K key = unwrapKey(cursor.getKey());
126+
if (key != null) {
127+
consumer.accept(key, value);
128+
}
129+
}
130+
}
131+
}
132+
}
133+
134+
private static final class WeakKeyEquivalence extends Equivalence {
135+
136+
@Override
137+
public int hashCode(Object o) {
138+
return o.hashCode();
139+
}
140+
141+
@Override
142+
public boolean equals(Object a, Object b) {
143+
boolean aIsWeak = a instanceof WeakKey<?>;
144+
boolean bIsWeak = b instanceof WeakKey<?>;
145+
if (aIsWeak && !bIsWeak) {
146+
return Objects.equals(((WeakKey<?>) a).get(), b);
147+
} else if (!aIsWeak && bIsWeak) {
148+
return Objects.equals(a, ((WeakKey<?>) b).get());
149+
}
150+
return a.equals(b);
151+
}
152+
153+
}
154+
155+
}

truffle/src/com.oracle.truffle.object/src/com/oracle/truffle/object/ImmutableMap.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -63,4 +63,27 @@ public interface ImmutableMap<K, V> extends Map<K, V> {
6363
*/
6464
ImmutableMap<K, V> copyAndRemove(K key);
6565

66+
@Override
67+
default V put(final K key, final V value) {
68+
throw unmodifiableException();
69+
}
70+
71+
@Override
72+
default void putAll(final Map<? extends K, ? extends V> m) {
73+
throw unmodifiableException();
74+
}
75+
76+
@Override
77+
default V remove(final Object key) {
78+
throw unmodifiableException();
79+
}
80+
81+
@Override
82+
default void clear() {
83+
throw unmodifiableException();
84+
}
85+
86+
static RuntimeException unmodifiableException() {
87+
throw new UnsupportedOperationException();
88+
}
6689
}

truffle/src/com.oracle.truffle.object/src/com/oracle/truffle/object/ObjectStorageOptions.java

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* The Universal Permissive License (UPL), Version 1.0
@@ -40,38 +40,28 @@
4040
*/
4141
package com.oracle.truffle.object;
4242

43-
/** @since 0.17 or earlier */
44-
@Deprecated
45-
public final class ObjectStorageOptions {
43+
final class ObjectStorageOptions {
4644
@SuppressWarnings("deprecation") private static final String OPTION_PREFIX = com.oracle.truffle.api.object.Layout.OPTION_PREFIX;
4745

4846
private ObjectStorageOptions() {
4947
}
5048

51-
/** @since 0.17 or earlier */
5249
public static final boolean PrimitiveLocations = booleanOption(OPTION_PREFIX + "PrimitiveLocations", true);
53-
/** @since 0.17 or earlier */
5450
public static final boolean IntegerLocations = booleanOption(OPTION_PREFIX + "IntegerLocations", true);
55-
/** @since 0.17 or earlier */
5651
public static final boolean DoubleLocations = booleanOption(OPTION_PREFIX + "DoubleLocations", true);
57-
/** @since 0.17 or earlier */
5852
public static final boolean LongLocations = booleanOption(OPTION_PREFIX + "LongLocations", true);
59-
/** @since 0.17 or earlier */
6053
public static final boolean BooleanLocations = booleanOption(OPTION_PREFIX + "BooleanLocations", true);
61-
/** @since 0.17 or earlier */
6254
public static final boolean TypedObjectLocations = booleanOption(OPTION_PREFIX + "TypedObjectLocations", true);
6355

6456
/**
6557
* Allocation of in-object fields.
66-
*
67-
* @since 0.17 or earlier
6858
*/
6959
public static final boolean InObjectFields = booleanOption(OPTION_PREFIX + "InObjectFields", true);
7060

7161
static final boolean TriePropertyMap = booleanOption(OPTION_PREFIX + "TriePropertyMap", true);
62+
static final boolean TrieTransitionMap = booleanOption(OPTION_PREFIX + "TrieTransitionMap", true);
7263

7364
// Debug options (should be final)
74-
/** @since 0.17 or earlier */
7565
public static final boolean TraceReshape = booleanOption(OPTION_PREFIX + "TraceReshape", false);
7666

7767
static final boolean DebugCounters = booleanOption(OPTION_PREFIX + "DebugCounters", false);
@@ -83,12 +73,9 @@ private ObjectStorageOptions() {
8373
static final boolean DumpShapes = DumpShapesDOT || DumpShapesJSON || DumpShapesIGV;
8474
static final String DumpShapesPath = System.getProperty(OPTION_PREFIX + "DumpShapesPath", "");
8575

86-
/** @since 0.17 or earlier */
8776
static final boolean Profile = booleanOption(OPTION_PREFIX + "Profile", false);
88-
/** @since 0.17 or earlier */
8977
static final int ProfileTopResults = Integer.getInteger(OPTION_PREFIX + "ProfileTopResults", -1);
9078

91-
/** @since 0.17 or earlier */
9279
public static boolean booleanOption(String name, boolean defaultValue) {
9380
String value = System.getProperty(name);
9481
return value == null ? defaultValue : value.equalsIgnoreCase("true");

0 commit comments

Comments
 (0)