From 678a8ea4a7cb2479774c2c587bbc9727cc9167bf Mon Sep 17 00:00:00 2001 From: SirYwell Date: Fri, 20 Dec 2024 10:00:39 +0100 Subject: [PATCH] Allow lazy-loading chunk section data when using Vector API --- .../core/extent/filter/CountFilter.java | 5 +- .../core/extent/filter/LinkedFilter.java | 8 +-- .../core/extent/filter/MaskFilter.java | 16 +++-- .../extent/filter/block/CharFilterBlock.java | 6 +- .../core/internal/simd/SimdSupport.java | 20 +++--- .../core/internal/simd/VectorFacade.java | 61 +++++++++++++++++++ .../simd/VectorizedCharFilterBlock.java | 17 +++--- .../core/internal/simd/VectorizedFilter.java | 4 +- .../core/internal/simd/VectorizedMask.java | 41 +++++++------ 9 files changed, 124 insertions(+), 54 deletions(-) create mode 100644 worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorFacade.java diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/CountFilter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/CountFilter.java index 7c6767bf2..e07bd276b 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/CountFilter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/CountFilter.java @@ -1,8 +1,8 @@ package com.fastasyncworldedit.core.extent.filter; import com.fastasyncworldedit.core.extent.filter.block.FilterBlock; +import com.fastasyncworldedit.core.internal.simd.VectorFacade; import com.fastasyncworldedit.core.internal.simd.VectorizedFilter; -import jdk.incubator.vector.ShortVector; import jdk.incubator.vector.VectorMask; public class CountFilter extends ForkedFilter implements VectorizedFilter { @@ -37,9 +37,8 @@ public class CountFilter extends ForkedFilter implements Vectorized } @Override - public ShortVector applyVector(final ShortVector get, final ShortVector set, VectorMask mask) { + public void applyVector(final VectorFacade get, final VectorFacade set, final VectorMask mask) { total += mask.trueCount(); - return set; } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/LinkedFilter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/LinkedFilter.java index ff57a4625..17571e3af 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/LinkedFilter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/LinkedFilter.java @@ -1,11 +1,11 @@ package com.fastasyncworldedit.core.extent.filter; import com.fastasyncworldedit.core.extent.filter.block.FilterBlock; +import com.fastasyncworldedit.core.internal.simd.VectorFacade; import com.fastasyncworldedit.core.internal.simd.VectorizedFilter; import com.fastasyncworldedit.core.queue.Filter; import com.fastasyncworldedit.core.queue.IChunk; import com.sk89q.worldedit.regions.Region; -import jdk.incubator.vector.ShortVector; import jdk.incubator.vector.VectorMask; import org.jetbrains.annotations.Nullable; @@ -78,9 +78,9 @@ public sealed class LinkedFilter implements } @Override - public ShortVector applyVector(final ShortVector get, final ShortVector set, VectorMask mask) { - ShortVector res = getLeft().applyVector(get, set, mask); - return getRight().applyVector(get, res, mask); + public void applyVector(final VectorFacade get, final VectorFacade set, final VectorMask mask) { + getLeft().applyVector(get, set, mask); + getRight().applyVector(get, set, mask); } @Override diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/MaskFilter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/MaskFilter.java index 2ce1527df..9ff02e3c4 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/MaskFilter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/MaskFilter.java @@ -2,6 +2,7 @@ package com.fastasyncworldedit.core.extent.filter; import com.fastasyncworldedit.core.extent.filter.block.DelegateFilter; import com.fastasyncworldedit.core.extent.filter.block.FilterBlock; +import com.fastasyncworldedit.core.internal.simd.VectorFacade; import com.fastasyncworldedit.core.internal.simd.SimdSupport; import com.fastasyncworldedit.core.internal.simd.VectorizedFilter; import com.fastasyncworldedit.core.internal.simd.VectorizedMask; @@ -11,6 +12,7 @@ import com.sk89q.worldedit.function.mask.Mask; import jdk.incubator.vector.ShortVector; import jdk.incubator.vector.VectorMask; import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; @@ -82,13 +84,15 @@ public class MaskFilter extends DelegateFilter { } @Override - public ShortVector applyVector(final ShortVector get, final ShortVector set, VectorMask mask) { + public void applyVector(final VectorFacade get, final VectorFacade set, final VectorMask mask) { final T parent = getParent(); - VectorMask masked = vectorizedMask.compareVector(set, get); - ShortVector res = parent.applyVector(get, set, mask.and(masked)); - VectorMask changed = res.compare(VectorOperators.NE, set); - changes.getAndAdd(changed.trueCount()); - return res; + final VectorSpecies species = mask.vectorSpecies(); + VectorMask masked = this.vectorizedMask.compareVector(set, get, species); + ShortVector before = set.getOrZero(masked.vectorSpecies()); + parent.applyVector(get, set, mask.and(masked)); + ShortVector after = set.getOrZero(masked.vectorSpecies()); + VectorMask changed = after.compare(VectorOperators.NE, before); + this.changes.getAndAdd(changed.trueCount()); } @Override diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/block/CharFilterBlock.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/block/CharFilterBlock.java index 718171efd..e7d527363 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/block/CharFilterBlock.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/extent/filter/block/CharFilterBlock.java @@ -37,14 +37,14 @@ public class CharFilterBlock extends ChunkFilterBlock { private int maxLayer; private int minLayer; - private CharGetBlocks get; - private IChunkSet set; + protected CharGetBlocks get; + protected IChunkSet set; protected char[] getArr; @Nullable protected char[] setArr; protected SetDelegate delegate; // local - private int layer; + protected int layer; private int index; private int x; private int y; diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/SimdSupport.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/SimdSupport.java index eefdc1929..d2c3b8a57 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/SimdSupport.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/SimdSupport.java @@ -54,7 +54,7 @@ public class SimdSupport { if (base == null) { yield null; } - yield (set, get) -> base.compareVector(set, get).not(); + yield (set, get, species) -> base.compareVector(set, get, species).not(); } default -> null; }; @@ -62,15 +62,15 @@ public class SimdSupport { private static VectorizedMask vectorizedTargetMaskNonAir() { // everything > VOID_AIR is not air - return (set, get) -> get.compare(VectorOperators.UNSIGNED_GT, BlockTypesCache.ReservedIDs.VOID_AIR); + return (set, get, species) -> get.get(species).compare(VectorOperators.UNSIGNED_GT, BlockTypesCache.ReservedIDs.VOID_AIR); } private static VectorizedMask vectorizedTargetMask(char ordinal) { - return (set, get) -> get.compare(VectorOperators.EQ, (short) ordinal); + return (set, get, species) -> get.get(species).compare(VectorOperators.EQ, (short) ordinal); } private static VectorizedMask vectorizedTargetMaskInverse(char ordinal) { - return (set, get) -> get.compare(VectorOperators.NE, (short) ordinal); + return (set, get, species) -> get.get(species).compare(VectorOperators.NE, (short) ordinal); } public static @Nullable VectorizedFilter vectorizedPattern(Pattern pattern) { @@ -102,14 +102,16 @@ public class SimdSupport { } @Override - public ShortVector applyVector(final ShortVector get, final ShortVector set, VectorMask mask) { - // only change the lanes the mask dictates us to change, keep the rest - return set.blend(ShortVector.broadcast(ShortVector.SPECIES_PREFERRED, ordinal), mask); + public Filter newInstance(final Filter other) { + return new VectorizedPattern<>(other, ordinal); } @Override - public Filter newInstance(final Filter other) { - return new VectorizedPattern<>(other, ordinal); + public void applyVector(final VectorFacade get, final VectorFacade set, final VectorMask mask) { + ShortVector s = set.getOrZero(mask.vectorSpecies()); + // only change the lanes the mask dictates us to change, keep the rest + s = s.blend(ShortVector.broadcast(ShortVector.SPECIES_PREFERRED, ordinal), mask); + set.setOrIgnore(s); } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorFacade.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorFacade.java new file mode 100644 index 000000000..6c703e0bd --- /dev/null +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorFacade.java @@ -0,0 +1,61 @@ +package com.fastasyncworldedit.core.internal.simd; + +import com.fastasyncworldedit.core.queue.IBlocks; +import com.sk89q.worldedit.world.block.BlockTypesCache; +import jdk.incubator.vector.ShortVector; +import jdk.incubator.vector.VectorSpecies; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class VectorFacade { + private final IBlocks blocks; + private int layer; + private int index; + private char[] data; + + VectorFacade(final IBlocks blocks) { + this.blocks = blocks; + } + + public ShortVector get(VectorSpecies species) { + if (this.data == null) { + load(); + } + return ShortVector.fromCharArray(species, this.data, this.index); + } + + public ShortVector getOrZero(VectorSpecies species) { + if (this.data == null) { + return species.zero().reinterpretAsShorts(); + } + return ShortVector.fromCharArray(species, this.data, this.index); + } + + public void setOrIgnore(ShortVector vector) { + if (this.data == null) { + if (vector.eq((short) BlockTypesCache.ReservedIDs.__RESERVED__).allTrue()) { + return; + } + load(); + } + vector.intoCharArray(this.data, this.index); + } + + private void load() { + this.data = this.blocks.load(this.layer); + } + + public void setLayer(int layer) { + this.layer = layer; + this.data = null; + } + + public void setIndex(int index) { + this.index = index; + } + + public void setData(char[] data) { + this.data = data; + } + +} diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedCharFilterBlock.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedCharFilterBlock.java index 9f557b134..5da9a4628 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedCharFilterBlock.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedCharFilterBlock.java @@ -19,18 +19,17 @@ public class VectorizedCharFilterBlock extends CharFilterBlock { throw new IllegalStateException("Unexpected VectorizedCharFilterBlock " + filter); } final VectorSpecies species = ShortVector.SPECIES_PREFERRED; - // TODO can we avoid eager initSet? - initSet(); // set array is null before - char[] setArr = this.setArr; - assert setArr != null; - char[] getArr = this.getArr; + VectorFacade setFassade = new VectorFacade(this.set); + setFassade.setLayer(this.layer); + VectorFacade getFassade = new VectorFacade(this.get); + getFassade.setLayer(this.layer); + getFassade.setData(this.getArr); // assume setArr.length == getArr.length == 4096 VectorMask affectAll = species.maskAll(true); for (int i = 0; i < 4096; i += species.length()) { - ShortVector set = ShortVector.fromCharArray(species, setArr, i); - ShortVector get = ShortVector.fromCharArray(species, getArr, i); - ShortVector res = vecFilter.applyVector(get, set, affectAll); - res.intoCharArray(setArr, i); + setFassade.setIndex(i); + getFassade.setIndex(i); + vecFilter.applyVector(getFassade, setFassade, affectAll); } } } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedFilter.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedFilter.java index b0801fcd0..1a7ad4004 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedFilter.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedFilter.java @@ -1,7 +1,6 @@ package com.fastasyncworldedit.core.internal.simd; import com.fastasyncworldedit.core.queue.Filter; -import jdk.incubator.vector.ShortVector; import jdk.incubator.vector.VectorMask; public interface VectorizedFilter extends Filter { @@ -12,8 +11,7 @@ public interface VectorizedFilter extends Filter { * @param get the get vector * @param set the set vector * @param mask the mask with the lanes set to true which should be affected by the filter - * @return the resulting set vector. */ - ShortVector applyVector(ShortVector get, ShortVector set, VectorMask mask); + void applyVector(VectorFacade get, VectorFacade set, VectorMask mask); } diff --git a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedMask.java b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedMask.java index a6fd18b48..9f44f2fb7 100644 --- a/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedMask.java +++ b/worldedit-core/src/main/java/com/fastasyncworldedit/core/internal/simd/VectorizedMask.java @@ -11,43 +11,50 @@ import jdk.incubator.vector.VectorSpecies; public interface VectorizedMask { default void processChunks(IChunk chunk, IChunkGet get, IChunkSet set) { + VectorFacade setFassade = new VectorFacade(set); + VectorFacade getFassade = new VectorFacade(get); for (int layer = get.getMinSectionPosition(); layer <= get.getMaxSectionPosition(); layer++) { + setFassade.setLayer(layer); + getFassade.setLayer(layer); final char[] sectionSet = set.loadIfPresent(layer); if (sectionSet == null) { continue; } - final char[] sectionGet = get.load(layer); - processSection(layer, sectionSet, sectionGet); + setFassade.setData(sectionSet); + processSection(layer, setFassade, getFassade); } } - default void processSection(int layer, char[] set, char[] get) { + default void processSection(int layer, VectorFacade set, VectorFacade get) { final VectorSpecies species = ShortVector.SPECIES_PREFERRED; - // assume that set.length % species.elementSize() == 0 - for (int i = 0; i < set.length; i += species.length()) { - ShortVector vectorSet = ShortVector.fromCharArray(species, set, i); - ShortVector vectorGet = ShortVector.fromCharArray(species, get, i); - vectorSet = processVector(vectorSet, vectorGet); - vectorSet.intoCharArray(set, i); + // assume that chunk sections have length 16 * 16 * 16 == 4096 + for (int i = 0; i < 4096; i += species.length()) { + set.setIndex(i); + get.setIndex(i); + processVector(set, get, species); } } /** - * {@return the set vector with all lanes that do not match this mask set to 0} + * Clears all blocks that aren't covered by the mask. * - * @param set the set vector - * @param get the get vector + * @param set the set vector + * @param get the get vector + * @param species the species to use */ - default ShortVector processVector(ShortVector set, ShortVector get) { - return set.blend(BlockTypesCache.ReservedIDs.__RESERVED__, compareVector(set, get).not()); + default void processVector(VectorFacade set, VectorFacade get, VectorSpecies species) { + ShortVector s = set.getOrZero(species); + s = s.blend(BlockTypesCache.ReservedIDs.__RESERVED__, compareVector(set, get, species).not()); + set.setOrIgnore(s); } /** * {@return a mask with all lanes set that match this mask} * - * @param set the set vector - * @param get the get vector + * @param set the set vector + * @param get the get vector + * @param species the species to use */ - VectorMask compareVector(ShortVector set, ShortVector get); + VectorMask compareVector(VectorFacade set, VectorFacade get, VectorSpecies species); }