RFR: 8330465: Stable Values and Collections (Internal)
Chen Liang
liach at openjdk.org
Tue May 14 14:14:21 UTC 2024
On Tue, 16 Apr 2024 11:47:23 GMT, Per Minborg <pminborg at openjdk.org> wrote:
> # Stable Values & Collections (Internal)
>
> ## Summary
> This PR proposes to introduce an internal _Stable Values & Collections_ API, which provides immutable value holders where elements are initialized _at most once_. Stable Values & Collections offer the performance and safety benefits of final fields while offering greater flexibility as to the timing of initialization.
>
> ## Goals
> * Provide an easy and intuitive API to describe value holders that can change at most once.
> * Decouple declaration from initialization without significant footprint or performance penalties.
> * Reduce the amount of static initializer and/or field initialization code.
> * Uphold integrity and consistency, even in a multi-threaded environment.
>
> For more details, see the draft JEP: https://openjdk.org/jeps/8312611
>
> ## Performance
> Performance compared to instance variables using an `AtomicReference` and one protected by double-checked locking under concurrent access by 8 threads:
>
>
> Benchmark Mode Cnt Score Error Units
> StableBenchmark.instanceAtomic avgt 10 1.576 ? 0.052 ns/op
> StableBenchmark.instanceDCL avgt 10 1.608 ? 0.059 ns/op
> StableBenchmark.instanceStable avgt 10 0.979 ? 0.023 ns/op <- StableValue (~40% faster than DCL)
>
>
> Performance compared to static variables protected by `AtomicReference`, class-holder idiom holder, and double-checked locking (8 threads):
>
>
> Benchmark Mode Cnt Score Error Units
> StableBenchmark.staticAtomic avgt 10 1.335 ? 0.056 ns/op
> StableBenchmark.staticCHI avgt 10 0.623 ? 0.086 ns/op
> StableBenchmark.staticDCL avgt 10 1.418 ? 0.171 ns/op
> StableBenchmark.staticList avgt 10 0.617 ? 0.024 ns/op
> StableBenchmark.staticStable avgt 10 0.604 ? 0.022 ns/op <- StableValue ( > 2x faster than `AtomicInteger` and DCL)
>
>
> Performance for stable lists in both instance and static contexts whereby the sum of random contents is calculated for stable lists (which are thread-safe) compared to `ArrayList` instances (which are not thread-safe) (under single thread access):
>
>
> Benchmark Mode Cnt Score Error Units
> StableListSumBenchmark.instanceArrayList avgt 10 0.356 ? 0.005 ns/op
> StableListSumBenchmark.instanceList avgt 10 0.373 ? 0.017 ns/op <- Stable list
> StableListSumBenchmark.staticArrayList avgt 10 0.352 ? 0.002 ns/op
> StableListSumBenchmark.staticList avgt 10 0.356 ? 0.00...
Glad to see this! Some API design remarks.
Also, I want to mention a few important differences between `@Stable` and Stable Values:
Patterns:
1. Benign race (does not exist in StableValue API): multiple threads can create an instance and upload, any non-null instance is functionally equivalent so race is ok (seen in most of JDK)
2. compareAndSet (setIfUnset): multiple threads can create instance, only one will succeed in uploading; usually for when the instance computation is cheap but we want single witness.
3. atomic computation (computeIfUnset): only one thread can create instance which will be witnessed by other threads; this pattern ensures correctness and prevents wasteful computation by other threads at the cost of locking and lambda creation.
Allocation in objects: `@Stable` field is local to an object but `StableValue` is another object; thus sharing strategy may differ, as stable fields are copied over but StableValue uses a shared cache (which is even better for avoiding redundant computation)
Question:
1. Will we ever try to expose the stable benign race model to users?
2. Will we ever try to inline the stable values in object layout like a stable field?
Just curious, can you test other samples, like `StableValue<List<Object>>` where the contained `List` is an immutable list from `List.of` factories? I think that would be a meaningful case too.
Also on a side note, I just realized there's no equivalent of `@Stable int[]` etc. stable primitive arrays exposed, yet immutable arrays will be useful. Is the Frozen Arrays JEP still active, or will this Stable Values consider expose stable primitive arrays?
src/java.base/share/classes/java/lang/reflect/Field.java line 179:
> 177: AccessibleObject.checkPermission();
> 178: // Always check if the field is a final StableValue
> 179: if (StableValue.class.isAssignableFrom(type) && Modifier.isFinal(modifiers)) {
This doesn't protect the Stable Collections.
src/java.base/share/classes/java/util/ImmutableCollections.java line 173:
> 171: .map(Objects::requireNonNull)
> 172: .toArray();
> 173: return keys instanceof EnumSet
We can move this instanceof check before the stream call.
src/java.base/share/classes/java/util/ImmutableCollections.java line 1457:
> 1455: private final V[] elements;
> 1456: @Stable
> 1457: private final AuxiliaryArrays aux;
Is java.util not trusted package so we need `@Stable`?
src/java.base/share/classes/java/util/ImmutableCollections.java line 1519:
> 1517: // Internal interface used to indicate the presence of
> 1518: // the computeIfUnset method that is unique to StableMap and StableEnumMap
> 1519: interface HasComputeIfUnset<K, V> {
Suggestion:
interface HasComputeIfUnset<K, V> extends Map<K, StableValue<V>> {
So maybe we can use pattern matching like:
Map<K, StableValue<V>> map = ...
if (map instanceof HasComputeIfUnset<K, V> hciu) {
// stuff
}
src/java.base/share/classes/java/util/ImmutableCollections.java line 1668:
> 1666: @Override
> 1667: public Set<Map.Entry<K, StableValue<V>>> entrySet() {
> 1668: return new AbstractSet<>() {
Maybe we want to do `AbstractImmutableSet` like in #18522.
src/java.base/share/classes/java/util/ImmutableCollections.java line 1677:
> 1675: static final class StableEnumMap<K extends Enum<K>, V>
> 1676: extends AbstractImmutableMap<K, StableValue<V>>
> 1677: implements Map<K, StableValue<V>>, HasComputeIfUnset<K, V> {
Note that this might be a navigable map, as enums are comparable.
src/java.base/share/classes/java/util/ImmutableCollections.java line 1855:
> 1853: @Override
> 1854: public boolean equals(Object o) {
> 1855: return o == this;
These implementations are violations to the Set contracts; Set's hash code should be its elements' sum (thus an entry set's hash code is equivalent to its map's hash) and equals should check if all elements are present. This also makes two entry sets from two `entrySet()` calls not equal (at least before valhalla)
src/java.base/share/classes/jdk/internal/lang/StableValue.java line 223:
> 221: /**
> 222: * {@return an unmodifiable, shallowly immutable, thread-safe, value-stable,
> 223: * {@linkplain Map } where the {@linkplain java.util.Map#keySet() keys}
Suggestion:
* {@linkplain Map} where the {@linkplain java.util.Map#keySet() keys}
src/java.base/share/classes/jdk/internal/lang/StableValue.java line 279:
> 277: static <V> V computeIfUnset(List<StableValue<V>> list,
> 278: int index,
> 279: IntFunction<? extends V> mapper) {
Hmm, these APIs seem unintuitive and error-prone to users. Have you studied the use case where for one stable list/map, there are distinct initialization logics for different indices/keys so that they support different mappers for the same list/map? I cannot recall on top of my head.
If we drop said ability and restrict mappers to the list/map creation, the whole thing will be much cleaner, and it's a better way to avoid capturing lambdas as well. Users can still go to individual stable values and use functional creation if they really, really want that functionality.
src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java line 90:
> 88: // to provide protection against store/store reordering.
> 89: // See VarHandle::releaseFence
> 90: UNSAFE.storeFence();
Can we use a storeStoreFence like in #18505?
-------------
PR Review: https://git.openjdk.org/jdk/pull/18794#pullrequestreview-2004581002
PR Comment: https://git.openjdk.org/jdk/pull/18794#issuecomment-2061350087
PR Comment: https://git.openjdk.org/jdk/pull/18794#issuecomment-2075216073
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1581828312
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1581828721
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1598676330
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1581829255
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1581829489
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1567930612
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1599908641
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1581830287
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1567944454
PR Review Comment: https://git.openjdk.org/jdk/pull/18794#discussion_r1581830884
More information about the compiler-dev
mailing list