mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 05:50:39 +00:00
Thread-id table for engine frames
- frames live in a 256-slot table indexed by thread id, ThreadLocal only on slot collisions - the single ThreadLocal probed long collision chains in the workers' thread-local maps
This commit is contained in:
1 parent
f2236a8482
commit
34f00d86cf
2 files changed
+85
-16
No files matched your search
@@ -23,24 +23,38 @@ import art.arcane.iris.generation.runtime.IrisEngine.BiomeEnvironmentBinding;
|
|||||||
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeBinding;
|
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeBinding;
|
||||||
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeScope;
|
import art.arcane.iris.generation.runtime.IrisEngine.GenerationRuntimeScope;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every per-thread engine binding (runtime assembly, biome environment, generation runtime scope) in one frame,
|
* Every per-thread engine binding (runtime assembly, biome environment, generation runtime scope) in one frame.
|
||||||
* so the engine getters on generation hot paths pay a single ThreadLocal lookup.
|
* The engine getters run on generation hot paths, where a ThreadLocal lookup probes long collision chains in the
|
||||||
|
* workers' crowded thread-local maps. Frames therefore live in a table indexed by thread id and owned by their
|
||||||
|
* thread; a frame whose slot is taken by another thread falls back to a ThreadLocal.
|
||||||
*/
|
*/
|
||||||
final class EngineThreadState {
|
final class EngineThreadState {
|
||||||
private final ThreadLocal<Frame> frames = new ThreadLocal<>();
|
private static final int SLOTS = 256;
|
||||||
|
|
||||||
|
private final AtomicReferenceArray<Frame> slots = new AtomicReferenceArray<>(SLOTS);
|
||||||
|
private final ThreadLocal<Frame> overflow = new ThreadLocal<>();
|
||||||
|
private final AtomicInteger overflowFrames = new AtomicInteger();
|
||||||
|
|
||||||
Frame current() {
|
Frame current() {
|
||||||
return frames.get();
|
Thread thread = Thread.currentThread();
|
||||||
|
Frame frame = slots.getPlain(slot(thread));
|
||||||
|
if (frame != null && frame.owner == thread) {
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
return overflowFrames.get() == 0 ? null : overflow.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
GenerationRuntimeBinding binding() {
|
GenerationRuntimeBinding binding() {
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
return frame == null ? null : frame.binding;
|
return frame == null ? null : frame.binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
RuntimeAssembly assembly() {
|
RuntimeAssembly assembly() {
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
return frame == null ? null : frame.assembly;
|
return frame == null ? null : frame.assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +63,7 @@ final class EngineThreadState {
|
|||||||
frame().assembly = assembly;
|
frame().assembly = assembly;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
frame.assembly = null;
|
frame.assembly = null;
|
||||||
releaseIfEmpty(frame);
|
releaseIfEmpty(frame);
|
||||||
@@ -57,7 +71,7 @@ final class EngineThreadState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BiomeEnvironmentBinding environment() {
|
BiomeEnvironmentBinding environment() {
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
return frame == null ? null : frame.environment;
|
return frame == null ? null : frame.environment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +80,7 @@ final class EngineThreadState {
|
|||||||
frame().environment = environment;
|
frame().environment = environment;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
if (frame != null) {
|
if (frame != null) {
|
||||||
frame.environment = null;
|
frame.environment = null;
|
||||||
releaseIfEmpty(frame);
|
releaseIfEmpty(frame);
|
||||||
@@ -89,7 +103,7 @@ final class EngineThreadState {
|
|||||||
if (Thread.currentThread() != owner) {
|
if (Thread.currentThread() != owner) {
|
||||||
throw new IllegalStateException("Iris generation runtime scope closed from a different thread.");
|
throw new IllegalStateException("Iris generation runtime scope closed from a different thread.");
|
||||||
}
|
}
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
if ((frame == null ? null : frame.binding) != installed) {
|
if ((frame == null ? null : frame.binding) != installed) {
|
||||||
throw new IllegalStateException("Iris generation runtime scopes must close in LIFO order.");
|
throw new IllegalStateException("Iris generation runtime scopes must close in LIFO order.");
|
||||||
}
|
}
|
||||||
@@ -104,23 +118,45 @@ final class EngineThreadState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Frame frame() {
|
private Frame frame() {
|
||||||
Frame frame = frames.get();
|
Frame frame = current();
|
||||||
if (frame == null) {
|
if (frame != null) {
|
||||||
frame = new Frame();
|
return frame;
|
||||||
frames.set(frame);
|
}
|
||||||
|
Thread thread = Thread.currentThread();
|
||||||
|
frame = new Frame(thread);
|
||||||
|
if (!slots.compareAndSet(slot(thread), null, frame)) {
|
||||||
|
frame.overflow = true;
|
||||||
|
overflow.set(frame);
|
||||||
|
overflowFrames.incrementAndGet();
|
||||||
}
|
}
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseIfEmpty(Frame frame) {
|
private void releaseIfEmpty(Frame frame) {
|
||||||
if (frame.assembly == null && frame.environment == null && frame.binding == null) {
|
if (frame.assembly != null || frame.environment != null || frame.binding != null) {
|
||||||
frames.remove();
|
return;
|
||||||
}
|
}
|
||||||
|
if (frame.overflow) {
|
||||||
|
overflow.remove();
|
||||||
|
overflowFrames.decrementAndGet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
slots.compareAndSet(slot(frame.owner), frame, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int slot(Thread thread) {
|
||||||
|
return (int) thread.threadId() & (SLOTS - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class Frame {
|
static final class Frame {
|
||||||
|
private final Thread owner;
|
||||||
|
private boolean overflow;
|
||||||
RuntimeAssembly assembly;
|
RuntimeAssembly assembly;
|
||||||
BiomeEnvironmentBinding environment;
|
BiomeEnvironmentBinding environment;
|
||||||
GenerationRuntimeBinding binding;
|
GenerationRuntimeBinding binding;
|
||||||
|
|
||||||
|
private Frame(Thread owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,39 @@ public class EngineThreadStateTest {
|
|||||||
assertNull(state.current());
|
assertNull(state.current());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void threadsSharingATableSlotKeepSeparateFrames() throws InterruptedException {
|
||||||
|
EngineThreadState state = new EngineThreadState();
|
||||||
|
IrisEngine.GenerationRuntimeBinding mainBinding = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
IrisEngine.GenerationRuntimeBinding otherBinding = new IrisEngine.GenerationRuntimeBinding(
|
||||||
|
mock(IrisEngine.class), mock(GenerationRuntime.class));
|
||||||
|
IrisEngine.GenerationRuntimeScope mainScope = state.open(mainBinding);
|
||||||
|
long slot = Thread.currentThread().threadId() & 255;
|
||||||
|
AtomicReference<Object> otherSeen = new AtomicReference<>();
|
||||||
|
AtomicReference<Object> otherAfterClose = new AtomicReference<>(otherBinding);
|
||||||
|
Thread colliding = null;
|
||||||
|
while (colliding == null) {
|
||||||
|
Thread candidate = new Thread(() -> {
|
||||||
|
IrisEngine.GenerationRuntimeScope scope = state.open(otherBinding);
|
||||||
|
otherSeen.set(state.binding());
|
||||||
|
scope.close();
|
||||||
|
otherAfterClose.set(state.current());
|
||||||
|
});
|
||||||
|
if ((candidate.threadId() & 255) == slot) {
|
||||||
|
colliding = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
colliding.start();
|
||||||
|
colliding.join();
|
||||||
|
|
||||||
|
assertSame(otherBinding, otherSeen.get());
|
||||||
|
assertNull(otherAfterClose.get());
|
||||||
|
assertSame(mainBinding, state.binding());
|
||||||
|
mainScope.close();
|
||||||
|
assertNull(state.current());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void framesAreThreadConfinedAndScopesRejectForeignClose() throws InterruptedException {
|
public void framesAreThreadConfinedAndScopesRejectForeignClose() throws InterruptedException {
|
||||||
EngineThreadState state = new EngineThreadState();
|
EngineThreadState state = new EngineThreadState();
|
||||||
|
|||||||
Reference in new issue
Block a user