-
Notifications
You must be signed in to change notification settings - Fork 138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix concurrency and performance issues #14
Changes from 1 commit
831d53e
60b4f02
a0318c3
0cc9840
4d92380
b7dff84
bc27ff0
089500a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,8 +4,6 @@ | |
|
||
import com.google.common.collect.Lists; | ||
import com.google.common.collect.Maps; | ||
import it.unimi.dsi.fastutil.objects.Object2IntMap; | ||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; | ||
import com.mojang.datafixers.DSL; | ||
import com.mojang.datafixers.DataFixUtils; | ||
import com.mojang.datafixers.types.Type; | ||
|
@@ -14,6 +12,9 @@ | |
import com.mojang.datafixers.types.templates.RecursivePoint; | ||
import com.mojang.datafixers.types.templates.TaggedChoice; | ||
import com.mojang.datafixers.types.templates.TypeTemplate; | ||
import it.unimi.dsi.fastutil.objects.Object2IntMap; | ||
import it.unimi.dsi.fastutil.objects.Object2IntMaps; | ||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
@@ -22,8 +23,8 @@ | |
import java.util.function.Supplier; | ||
|
||
public class Schema { | ||
protected final Object2IntMap<String> RECURSIVE_TYPES = new Object2IntOpenHashMap<>(); | ||
private final Map<String, Supplier<TypeTemplate>> TYPE_TEMPLATES = Maps.newHashMap(); | ||
protected final Object2IntMap<String> RECURSIVE_TYPES = Object2IntMaps.synchronize(new Object2IntOpenHashMap<>()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All schema construction should be done during bootstrap and frozen after that, synchronization inside buildTypes doesn't quite make sense since it's a one-off operation anyway. This class can hopefully be cleaned up slightly, and made to construct immutable maps instead, but synchronization is not the right approach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. I went a bit heavy handed to make sure I covered any risks, but looking at it closer I agree shouldn't be needed since it's all done in constructor. |
||
private final Map<String, Supplier<TypeTemplate>> TYPE_TEMPLATES = Maps.newConcurrentMap(); | ||
private final Map<String, Type<?>> TYPES; | ||
private final int versionKey; | ||
private final String name; | ||
|
@@ -39,25 +40,30 @@ public Schema(final int versionKey, final Schema parent) { | |
} | ||
|
||
protected Map<String, Type<?>> buildTypes() { | ||
final Map<String, Type<?>> types = Maps.newHashMap(); | ||
final Map<String, Type<?>> types = Maps.newConcurrentMap(); | ||
|
||
final List<TypeTemplate> templates = Lists.newArrayList(); | ||
|
||
for (final Object2IntMap.Entry<String> entry : RECURSIVE_TYPES.object2IntEntrySet()) { | ||
templates.add(DSL.check(entry.getKey(), entry.getIntValue(), getTemplate(entry.getKey()))); | ||
synchronized (RECURSIVE_TYPES) { | ||
for (final Object2IntMap.Entry<String> entry : RECURSIVE_TYPES.object2IntEntrySet()) { | ||
templates.add(DSL.check(entry.getKey(), entry.getIntValue(), getTemplate(entry.getKey()))); | ||
} | ||
} | ||
|
||
final TypeTemplate choice = templates.stream().reduce(DSL::or).get(); | ||
final TypeFamily family = new RecursiveTypeFamily(name, choice); | ||
|
||
for (final String name : TYPE_TEMPLATES.keySet()) { | ||
final Type<?> type; | ||
if (RECURSIVE_TYPES.containsKey(name)) { | ||
type = family.apply(RECURSIVE_TYPES.getInt(name)); | ||
} else { | ||
type = getTemplate(name).apply(family).apply(-1); | ||
synchronized (TYPE_TEMPLATES) { | ||
for (final String name : TYPE_TEMPLATES.keySet()) { | ||
final Type<?> type; | ||
int recurseId = RECURSIVE_TYPES.getOrDefault(name, -1); | ||
if (recurseId != -1) { | ||
type = family.apply(recurseId); | ||
} else { | ||
type = getTemplate(name).apply(family).apply(-1); | ||
} | ||
types.put(name, type); | ||
} | ||
types.put(name, type); | ||
} | ||
return types; | ||
} | ||
|
@@ -91,8 +97,9 @@ public TypeTemplate resolveTemplate(final String name) { | |
} | ||
|
||
public TypeTemplate id(final String name) { | ||
if (RECURSIVE_TYPES.containsKey(name)) { | ||
return DSL.id(RECURSIVE_TYPES.get(name)); | ||
int id = RECURSIVE_TYPES.getOrDefault(name, -1); | ||
if (id != -1) { | ||
return DSL.id(id); | ||
} | ||
return getTemplate(name); | ||
} | ||
|
@@ -140,8 +147,10 @@ public void register(final Map<String, Supplier<TypeTemplate>> map, final String | |
public void registerType(final boolean recursive, final DSL.TypeReference type, final Supplier<TypeTemplate> template) { | ||
TYPE_TEMPLATES.put(type.typeName(), template); | ||
// TODO: calculate recursiveness instead of hardcoding | ||
if (recursive && !RECURSIVE_TYPES.containsKey(type.typeName())) { | ||
RECURSIVE_TYPES.put(type.typeName(), RECURSIVE_TYPES.size()); | ||
synchronized (RECURSIVE_TYPES) { | ||
if (recursive && !RECURSIVE_TYPES.containsKey(type.typeName())) { | ||
RECURSIVE_TYPES.put(type.typeName(), RECURSIVE_TYPES.size()); | ||
} | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,8 +29,10 @@ | |
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
import java.util.concurrent.CompletableFuture; | ||
|
||
public abstract class Type<A> implements App<Type.Mu, A> { | ||
private static final Map<Triple<Type<?>, TypeRewriteRule, PointFreeRule>, CompletableFuture<Optional<? extends RewriteResult<?, ?>>>> PENDING_REWRITE_CACHE = Maps.newConcurrentMap(); | ||
private static final Map<Triple<Type<?>, TypeRewriteRule, PointFreeRule>, Optional<? extends RewriteResult<?, ?>>> REWRITE_CACHE = Maps.newConcurrentMap(); | ||
|
||
public static class Mu implements K1 {} | ||
|
@@ -157,11 +159,33 @@ public <T, B> T capWrite(final DynamicOps<T> ops, final Type<?> expectedType, fi | |
@SuppressWarnings("unchecked") | ||
public Optional<RewriteResult<A, ?>> rewrite(final TypeRewriteRule rule, final PointFreeRule fRule) { | ||
final Triple<Type<?>, TypeRewriteRule, PointFreeRule> key = Triple.of(this, rule, fRule); | ||
if (!REWRITE_CACHE.containsKey(key)) { | ||
final Optional<? extends RewriteResult<?, ?>> result = rule.rewrite(this).flatMap(r -> r.view().rewrite(fRule).map(view -> RewriteResult.create(view, r.recData()))); | ||
// This code under contention would generate multiple rewrites, so we use CompletableFuture for pending rewrites. | ||
// We can not use computeIfAbsent because this is a recursive call that will block server startup | ||
// during the Bootstrap phrase that's trying to pre cache these rewrites. | ||
Optional<? extends RewriteResult<?, ?>> rewrite = REWRITE_CACHE.get(key); | ||
//noinspection OptionalAssignedToNull | ||
if (rewrite != null) { | ||
return (Optional<RewriteResult<A, ?>>) rewrite; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cast should not be neccesary if the map stores There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not worth changing this. That breaks everything else. The class uses a generic but uses a static cache, so the cast is required unless the caches are moved to be instance caches instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the reason you failed is that you need another static field with an object that you sync around instead of the instance cache, isn't it? |
||
} | ||
CompletableFuture<Optional<? extends RewriteResult<?, ?>>> pending; | ||
boolean needsCreate; | ||
synchronized (PENDING_REWRITE_CACHE) { | ||
pending = PENDING_REWRITE_CACHE.get(key); | ||
needsCreate = pending == null; | ||
if (pending == null) { | ||
pending = new CompletableFuture<>(); | ||
PENDING_REWRITE_CACHE.put(key, pending); | ||
} | ||
} | ||
if (needsCreate) { | ||
Optional<RewriteResult<A, ?>> result = rule.rewrite(this).flatMap(r -> r.view().rewrite(fRule).map(view -> RewriteResult.create(view, r.recData()))); | ||
REWRITE_CACHE.put(key, result); | ||
pending.complete(result); | ||
PENDING_REWRITE_CACHE.remove(key); | ||
return result; | ||
} else { | ||
return (Optional<RewriteResult<A, ?>>) pending.join(); | ||
} | ||
return (Optional<RewriteResult<A, ?>>) REWRITE_CACHE.get(key); | ||
} | ||
|
||
public <FT, FR> Type<?> getSetType(final OpticFinder<FT> optic, final Type<FR> newType) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems too heavy-weight, since evalCached should be a hot spot. There might be a better option, like https://en.wikipedia.org/wiki/Double-checked_locking
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is something I was concerned about too and was considering a double checked lock also. I will update it.
Wasn't able to detect any performance concerns considering we have usage of datafixers all done on the thread pool now, but helping improve conversion speed is always good.