Optimise palette

Palette has 4 implementations: global, hash, linear, and single.

When only one implementation is being used (i.e all chunks
loaded have sections with linear palette) the JIT can optimise
the call to #valueFor quite well. When there are two implementations
(i.e hash, linear) the JIT can still optimise the call but at a
slight cost.

However, when there are three or more in use
(i.e linear, hash, single/global) the JIT cannot
optimise the call and must revert to an interface invoke - which
is terribly slow.

In order to optimise this, we can extract an array for
the hash, linear, and single palette implementations and
perform lookups on the array directly instead of invoking
This commit is contained in:
Spottedleaf
2024-05-22 14:33:00 -07:00
parent a1cf0d5c22
commit 8e5f550739
9 changed files with 289 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
package ca.spottedleaf.moonrise.mixin.fast_palette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalettedContainer;
import net.minecraft.core.IdMap;
import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(CrudeIncrementalIntIdentityHashBiMap.class)
public abstract class CrudeIncrementalIntIdentityHashBiMapMixin<K> implements IdMap<K>, FastPalette<K> {
@Shadow
private K[] byId;
@Unique
private FastPalettedContainer<K> reference;
@Override
public K[] moonrise$getRawPalette(final FastPalettedContainer<K> src) {
this.reference = src;
return this.byId;
}
/**
* @reason Hook to call back to paletted container to update raw palette array
* @author Spottedleaf
*/
@Inject(
method = "grow",
at = @At(
value = "RETURN"
)
)
private void growHook(final CallbackInfo ci) {
final FastPalettedContainer<K> ref = this.reference;
if (ref != null) {
ref.moonrise$updatePaletteArray(this.byId);
}
}
}

View File

@@ -0,0 +1,23 @@
package ca.spottedleaf.moonrise.mixin.fast_palette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalettedContainer;
import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap;
import net.minecraft.world.level.chunk.HashMapPalette;
import net.minecraft.world.level.chunk.Palette;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(HashMapPalette.class)
public abstract class HashMapPaletteMixin<T> implements Palette<T>, FastPalette<T> {
@Shadow
@Final
private CrudeIncrementalIntIdentityHashBiMap<T> values;
@Override
public T[] moonrise$getRawPalette(final FastPalettedContainer<T> container) {
return ((FastPalette<T>)this.values).moonrise$getRawPalette(container);
}
}

View File

@@ -0,0 +1,22 @@
package ca.spottedleaf.moonrise.mixin.fast_palette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalettedContainer;
import net.minecraft.world.level.chunk.LinearPalette;
import net.minecraft.world.level.chunk.Palette;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(LinearPalette.class)
public abstract class LinearPaletteMixin<T> implements Palette<T>, FastPalette<T> {
@Shadow
@Final
private T[] values;
@Override
public T[] moonrise$getRawPalette(final FastPalettedContainer<T> container) {
return this.values;
}
}

View File

@@ -0,0 +1,10 @@
package ca.spottedleaf.moonrise.mixin.fast_palette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalette;
import net.minecraft.world.level.chunk.Palette;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(Palette.class)
public interface PaletteMixin<T> extends FastPalette<T> {
}

View File

@@ -0,0 +1,146 @@
package ca.spottedleaf.moonrise.mixin.fast_palette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalettedContainer;
import net.minecraft.world.level.chunk.PaletteResize;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerRO;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(PalettedContainer.class)
public abstract class PalettedContainerMixin<T> implements PaletteResize<T>, PalettedContainerRO<T>, FastPalettedContainer<T> {
@Shadow
public volatile PalettedContainer.Data<T> data;
@Unique
private T[] rawPalette;
@Unique
private void updateData(final PalettedContainer.Data<T> data) {
if (data != null) {
this.rawPalette = ((FastPalette<T>)data.palette).moonrise$getRawPalette(this);
}
}
@Override
public void moonrise$updatePaletteArray(final T[] palette) {
this.rawPalette = palette;
}
/**
* @reason Hook to update raw palette data on object construction
* @author Spottedleaf
*/
@Inject(
method = "<init>(Lnet/minecraft/core/IdMap;Ljava/lang/Object;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;)V",
at = @At(
value = "RETURN"
)
)
private void constructorHook1(final CallbackInfo ci) {
this.updateData(this.data);
}
/**
* @reason Hook to update raw palette data on object construction
* @author Spottedleaf
*/
@Inject(
method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Configuration;Lnet/minecraft/util/BitStorage;Ljava/util/List;)V",
at = @At(
value = "RETURN"
)
)
private void constructorHook2(final CallbackInfo ci) {
this.updateData(this.data);
}
/**
* @reason Hook to update raw palette data on object construction
* @author Spottedleaf
*/
@Inject(
method = "<init>(Lnet/minecraft/core/IdMap;Lnet/minecraft/world/level/chunk/PalettedContainer$Strategy;Lnet/minecraft/world/level/chunk/PalettedContainer$Data;)V",
at = @At(
value = "RETURN"
)
)
private void constructorHook3(final CallbackInfo ci) {
this.updateData(this.data);
}
/**
* @reason Hook to update raw palette data on palette resize
* @author Spottedleaf
*/
@Inject(
method = "onResize",
at = @At(
value = "RETURN"
)
)
private void resizeHook(final CallbackInfoReturnable<Integer> cir) {
this.updateData(this.data);
}
/**
* @reason Hook to update raw palette data on clientside read
* @author Spottedleaf
*/
@Inject(
method = "read",
at = @At(
value = "RETURN"
)
)
private void readHook(final CallbackInfo ci) {
this.updateData(this.data);
}
@Unique
private T readPaletteSlow(final int paletteIdx) {
return this.data.palette.valueFor(paletteIdx);
}
@Unique
private T readPalette(final int paletteIdx) {
final T[] palette = this.rawPalette;
if (palette == null) {
return this.readPaletteSlow(paletteIdx);
}
final T ret = palette[paletteIdx];
if (ret == null) {
throw new IllegalArgumentException("Palette index out of bounds");
}
return ret;
}
/**
* @reason Replace palette read with optimised version
* @author Spottedleaf
*/
@Overwrite
private T getAndSet(final int index, final T value) {
final int paletteIdx = this.data.palette.idFor(value);
final int prev = this.data.storage.getAndSet(index, paletteIdx);
return this.readPalette(prev);
}
/**
* @reason Replace palette read with optimised version
* @author Spottedleaf
*/
@Overwrite
public T get(final int index) {
return this.readPalette(this.data.storage.get(index));
}
}

View File

@@ -0,0 +1,20 @@
package ca.spottedleaf.moonrise.mixin.fast_palette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalette;
import ca.spottedleaf.moonrise.patches.fast_palette.FastPalettedContainer;
import net.minecraft.world.level.chunk.Palette;
import net.minecraft.world.level.chunk.SingleValuePalette;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
@Mixin(SingleValuePalette.class)
public abstract class SingleValuePaletteMixin<T> implements Palette<T>, FastPalette<T> {
@Shadow
private T value;
@Override
public T[] moonrise$getRawPalette(final FastPalettedContainer<T> container) {
return (T[])new Object[] { this.value };
}
}

View File

@@ -0,0 +1,9 @@
package ca.spottedleaf.moonrise.patches.fast_palette;
public interface FastPalette<T> {
public default T[] moonrise$getRawPalette(final FastPalettedContainer<T> src) {
return null;
}
}

View File

@@ -0,0 +1,7 @@
package ca.spottedleaf.moonrise.patches.fast_palette;
public interface FastPalettedContainer<T> {
public void moonrise$updatePaletteArray(final T[] palette);
}

View File

@@ -39,6 +39,12 @@
"explosions.ExplosionMixin",
"explosions.ExplosionProfileMixin",
"farm_block.FarmBlockMixin",
"fast_palette.CrudeIncrementalIntIdentityHashBiMapMixin",
"fast_palette.HashMapPaletteMixin",
"fast_palette.LinearPaletteMixin",
"fast_palette.PalettedContainerMixin",
"fast_palette.PaletteMixin",
"fast_palette.SingleValuePaletteMixin",
"fluid.FlowingFluidMixin",
"fluid.FluidStateMixin",
"keep_alive_client.ServerGamePacketListenerImplMixin",