Avoid slow synchronized path during reference building

This commit is contained in:
themode 2022-05-04 18:24:15 +02:00
parent fdd3e2c53c
commit c009ef0505

View File

@ -8,7 +8,8 @@ import java.util.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
final class SnapshotUpdaterImpl implements SnapshotUpdater { final class SnapshotUpdaterImpl implements SnapshotUpdater {
private final Map<Snapshotable, AtomicReference<Snapshot>> referenceMap = new IdentityHashMap<>(); private final IdentityHashMap<Snapshotable, AtomicReference<Snapshot>> referenceMap = new IdentityHashMap<>();
private IdentityHashMap<Snapshotable, AtomicReference<Snapshot>> readOnlyReferenceMap;
private List<Entry> queue = new ArrayList<>(); private List<Entry> queue = new ArrayList<>();
static <T extends Snapshot> @NotNull T update(@NotNull Snapshotable snapshotable) { static <T extends Snapshot> @NotNull T update(@NotNull Snapshotable snapshotable) {
@ -19,13 +20,22 @@ final class SnapshotUpdaterImpl implements SnapshotUpdater {
} }
@Override @Override
public synchronized <T extends Snapshot> @NotNull AtomicReference<T> reference(@NotNull Snapshotable snapshotable) { public <T extends Snapshot> @NotNull AtomicReference<T> reference(@NotNull Snapshotable snapshotable) {
AtomicReference<Snapshot> ref = new AtomicReference<>(); AtomicReference<Snapshot> ref;
// Very often the same snapshotable is referenced multiple times.
var readOnly = this.readOnlyReferenceMap;
if (readOnly != null && (ref = readOnly.get(snapshotable)) != null) {
return (AtomicReference<T>) ref;
}
// If this is a new snapshotable, we need to create a new reference.
synchronized (this) {
ref = new AtomicReference<>();
var prev = referenceMap.putIfAbsent(snapshotable, ref); var prev = referenceMap.putIfAbsent(snapshotable, ref);
if (prev != null) return (AtomicReference<T>) prev; if (prev != null) return (AtomicReference<T>) prev;
this.queue.add(new Entry(snapshotable, ref)); this.queue.add(new Entry(snapshotable, ref));
return (AtomicReference<T>) ref; return (AtomicReference<T>) ref;
} }
}
record Entry(Snapshotable snapshotable, AtomicReference<Snapshot> ref) { record Entry(Snapshotable snapshotable, AtomicReference<Snapshot> ref) {
} }
@ -34,6 +44,7 @@ final class SnapshotUpdaterImpl implements SnapshotUpdater {
List<Entry> temp; List<Entry> temp;
while (!(temp = new ArrayList<>(queue)).isEmpty()) { while (!(temp = new ArrayList<>(queue)).isEmpty()) {
queue = new ArrayList<>(); queue = new ArrayList<>();
readOnlyReferenceMap = (IdentityHashMap<Snapshotable, AtomicReference<Snapshot>>) referenceMap.clone();
temp.parallelStream().forEach(entry -> { temp.parallelStream().forEach(entry -> {
Snapshotable snap = entry.snapshotable; Snapshotable snap = entry.snapshotable;
entry.ref.set(Objects.requireNonNull(snap.updateSnapshot(this), "Snapshot must not be null after an update!")); entry.ref.set(Objects.requireNonNull(snap.updateSnapshot(this), "Snapshot must not be null after an update!"));