From d1fec88769aceadc1349bf5d7a6a33efe3372793 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Wed, 8 Jul 2026 17:55:14 -0400 Subject: [PATCH] d --- .../arcane/iris/modded/MainWorldService.java | 199 +++++++++ .../iris/modded/ModdedEngineBootstrap.java | 1 + .../arcane/iris/modded/ModdedModConfig.java | 44 +- .../modded/command/ModdedWorldCommands.java | 97 +++++ .../2026-06-27-x86-simd-validation-handoff.md | 145 ------- tools/simd-bench/README.md | 101 +++++ tools/simd-bench/out/simdbench/Bench.class | Bin 0 -> 12112 bytes .../out/simdbench/NoiseKernels2D.class | Bin 0 -> 208 bytes .../out/simdbench/ScalarNoiseKernels2D.class | Bin 0 -> 2168 bytes .../out/simdbench/ScalarSimdKernels.class | Bin 0 -> 770 bytes .../out/simdbench/SimdKernels.class | Bin 0 -> 224 bytes .../out/simdbench/VectorNoiseKernels2D.class | Bin 0 -> 7762 bytes .../out/simdbench/VectorSimdKernels.class | Bin 0 -> 3964 bytes tools/simd-bench/run.bat | 2 + tools/simd-bench/run.sh | 4 + tools/simd-bench/simd-bench.jar | Bin 0 -> 15234 bytes tools/simd-bench/src/simdbench/Bench.java | 387 ++++++++++++++++++ .../src/simdbench/NoiseKernels2D.java | 8 + .../src/simdbench/ScalarNoiseKernels2D.java | 99 +++++ .../src/simdbench/ScalarSimdKernels.java | 37 ++ .../simd-bench/src/simdbench/SimdKernels.java | 11 + .../src/simdbench/VectorNoiseKernels2D.java | 129 ++++++ .../src/simdbench/VectorSimdKernels.java | 74 ++++ 23 files changed, 1189 insertions(+), 149 deletions(-) create mode 100644 adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java delete mode 100644 docs/superpowers/2026-06-27-x86-simd-validation-handoff.md create mode 100644 tools/simd-bench/README.md create mode 100644 tools/simd-bench/out/simdbench/Bench.class create mode 100644 tools/simd-bench/out/simdbench/NoiseKernels2D.class create mode 100644 tools/simd-bench/out/simdbench/ScalarNoiseKernels2D.class create mode 100644 tools/simd-bench/out/simdbench/ScalarSimdKernels.class create mode 100644 tools/simd-bench/out/simdbench/SimdKernels.class create mode 100644 tools/simd-bench/out/simdbench/VectorNoiseKernels2D.class create mode 100644 tools/simd-bench/out/simdbench/VectorSimdKernels.class create mode 100644 tools/simd-bench/run.bat create mode 100755 tools/simd-bench/run.sh create mode 100644 tools/simd-bench/simd-bench.jar create mode 100644 tools/simd-bench/src/simdbench/Bench.java create mode 100644 tools/simd-bench/src/simdbench/NoiseKernels2D.java create mode 100644 tools/simd-bench/src/simdbench/ScalarNoiseKernels2D.java create mode 100644 tools/simd-bench/src/simdbench/ScalarSimdKernels.java create mode 100644 tools/simd-bench/src/simdbench/SimdKernels.java create mode 100644 tools/simd-bench/src/simdbench/VectorNoiseKernels2D.java create mode 100644 tools/simd-bench/src/simdbench/VectorSimdKernels.java diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java new file mode 100644 index 000000000..2906f6750 --- /dev/null +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/MainWorldService.java @@ -0,0 +1,199 @@ +/* + * Iris is a World Generator for Minecraft Servers + * Copyright (c) 2026 Arcane Arts (Volmit Software) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package art.arcane.iris.modded; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +public final class MainWorldService { + private static final Logger LOGGER = LoggerFactory.getLogger("Iris"); + private static final String PRESET_NAMESPACE = "irisworldgen"; + private static final String MARKER_NAME = "mainworld.pending"; + private static final String[] VANILLA_DIMENSION_FOLDERS = { + "region", + "entities", + "poi", + "mantle", + "dimensions/minecraft/overworld", + "dimensions/minecraft/the_nether", + "dimensions/minecraft/the_end", + "DIM-1", + "DIM1" + }; + + private MainWorldService() { + } + + public static String presetIdFor(String packRef) { + String value = packRef.trim(); + int colon = value.indexOf(':'); + String pack = colon >= 0 ? value.substring(0, colon) : value; + String dimension = colon >= 0 ? value.substring(colon + 1) : value; + String presetKey = dimension.equals(pack) ? pack : pack + "_" + dimension; + return PRESET_NAMESPACE + ":" + presetKey; + } + + public static void reconcileEarly() { + try { + ModdedModConfig config = ModdedModConfig.get(); + String pack = config.mainWorldPack(); + if (pack == null || pack.isBlank()) { + return; + } + Path properties = instanceRoot().resolve("server.properties"); + String target = presetIdFor(pack); + String currentType = readProperty(properties, "level-type"); + if (!target.equals(currentType)) { + writeLevelProperties(properties, target, config.mainWorldSeed()); + markPending(); + LOGGER.warn("Iris main world '{}' staged: server.properties level-type set to {}. Restart again to generate it (this boot still uses the previous overworld; player data is kept).", pack, target); + return; + } + if (!isPending()) { + return; + } + String levelName = firstNonBlank(readProperty(properties, "level-name"), "world"); + wipeVanillaDimensions(instanceRoot().resolve(levelName)); + clearPending(); + LOGGER.warn("Iris main world '{}' generated fresh: cleared the previous overworld/nether/end so this boot regenerates them as {} (player data kept).", pack, target); + } catch (Throwable e) { + LOGGER.error("Iris main world reconciliation failed", e); + } + } + + public static boolean stage(String packRef, long seed) { + try { + Path properties = instanceRoot().resolve("server.properties"); + writeLevelProperties(properties, presetIdFor(packRef), seed); + markPending(); + return true; + } catch (IOException e) { + LOGGER.error("Iris failed to stage the main world in server.properties", e); + return false; + } + } + + public static void clearOverride() { + try { + clearPending(); + } catch (IOException e) { + LOGGER.error("Iris failed to clear the pending main world marker", e); + } + } + + private static Path instanceRoot() { + return ModdedEngineBootstrap.loader().configDir().getParent(); + } + + private static Path markerFile() { + return ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve(MARKER_NAME); + } + + private static boolean isPending() { + return Files.isRegularFile(markerFile()); + } + + private static void markPending() throws IOException { + Path marker = markerFile(); + Files.createDirectories(marker.getParent()); + Files.writeString(marker, "pending", StandardCharsets.UTF_8); + } + + private static void clearPending() throws IOException { + Files.deleteIfExists(markerFile()); + } + + private static String readProperty(Path properties, String key) throws IOException { + if (!Files.isRegularFile(properties)) { + return null; + } + List lines = Files.readAllLines(properties, StandardCharsets.UTF_8); + String prefix = key + "="; + for (String line : lines) { + if (line.startsWith(prefix)) { + return unescape(line.substring(prefix.length()).trim()); + } + } + return null; + } + + private static void writeLevelProperties(Path properties, String target, long seed) throws IOException { + List lines = Files.isRegularFile(properties) + ? new ArrayList<>(Files.readAllLines(properties, StandardCharsets.UTF_8)) + : new ArrayList<>(); + setProperty(lines, "level-type", escape(target)); + if (seed != 0L) { + setProperty(lines, "level-seed", Long.toString(seed)); + } + Files.write(properties, lines, StandardCharsets.UTF_8); + } + + private static void setProperty(List lines, String key, String value) { + String prefix = key + "="; + for (int i = 0; i < lines.size(); i++) { + if (lines.get(i).startsWith(prefix)) { + lines.set(i, prefix + value); + return; + } + } + lines.add(prefix + value); + } + + private static void wipeVanillaDimensions(Path worldRoot) throws IOException { + Files.deleteIfExists(worldRoot.resolve("level.dat")); + Files.deleteIfExists(worldRoot.resolve("level.dat_old")); + for (String folder : VANILLA_DIMENSION_FOLDERS) { + deleteRecursively(worldRoot.resolve(folder)); + } + } + + private static void deleteRecursively(Path path) throws IOException { + if (!Files.exists(path)) { + return; + } + List entries = new ArrayList<>(); + try (Stream walk = Files.walk(path)) { + walk.sorted(Comparator.comparingInt(Path::getNameCount).reversed()).forEach(entries::add); + } + for (Path entry : entries) { + Files.deleteIfExists(entry); + } + } + + private static String escape(String value) { + return value.replace(":", "\\:"); + } + + private static String unescape(String value) { + return value.replace("\\:", ":").replace("\\=", "="); + } + + private static String firstNonBlank(String value, String fallback) { + return value == null || value.isBlank() ? fallback : value; + } +} diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java index 036b4d423..0cd06d720 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedEngineBootstrap.java @@ -116,6 +116,7 @@ public final class ModdedEngineBootstrap { ModdedIrisLog.info("Iris " + moddedLoader.modVersion() + " bootstrapping on Minecraft " + moddedLoader.minecraftVersion() + " (" + loaderDescription + ")"); selfTest(moddedLoader.getClass().getClassLoader()); bind(); + MainWorldService.reconcileEarly(); chunkGeneratorRegistration.run(); ModdedIrisLog.info("Iris chunk generator registered as irisworldgen:iris"); armParityProbe(); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java index 5d02e65f5..025e38cb6 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/ModdedModConfig.java @@ -36,12 +36,19 @@ public final class ModdedModConfig { private final boolean autoDownloadDefaultPack; private final String primaryWorld; private final boolean routePlayersToPrimaryWorld; + private final String mainWorldPack; + private final long mainWorldSeed; + private final boolean mainWorldAutoRestart; - private ModdedModConfig(String defaultPack, boolean autoDownloadDefaultPack, String primaryWorld, boolean routePlayersToPrimaryWorld) { + private ModdedModConfig(String defaultPack, boolean autoDownloadDefaultPack, String primaryWorld, boolean routePlayersToPrimaryWorld, + String mainWorldPack, long mainWorldSeed, boolean mainWorldAutoRestart) { this.defaultPack = defaultPack; this.autoDownloadDefaultPack = autoDownloadDefaultPack; this.primaryWorld = primaryWorld == null ? "" : primaryWorld.trim(); this.routePlayersToPrimaryWorld = routePlayersToPrimaryWorld; + this.mainWorldPack = mainWorldPack == null ? "" : mainWorldPack.trim(); + this.mainWorldSeed = mainWorldSeed; + this.mainWorldAutoRestart = mainWorldAutoRestart; } public static ModdedModConfig get() { @@ -61,7 +68,18 @@ public final class ModdedModConfig { public static void setPrimaryWorld(String dimensionId) { synchronized (LOCK) { ModdedModConfig current = get(); - ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, dimensionId, current.routePlayersToPrimaryWorld); + ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, dimensionId, current.routePlayersToPrimaryWorld, + current.mainWorldPack, current.mainWorldSeed, current.mainWorldAutoRestart); + instance = updated; + write(configFile(), updated); + } + } + + public static void setMainWorld(String packRef, long seed) { + synchronized (LOCK) { + ModdedModConfig current = get(); + ModdedModConfig updated = new ModdedModConfig(current.defaultPack, current.autoDownloadDefaultPack, current.primaryWorld, current.routePlayersToPrimaryWorld, + packRef == null ? "" : packRef.trim(), seed, current.mainWorldAutoRestart); instance = updated; write(configFile(), updated); } @@ -83,13 +101,25 @@ public final class ModdedModConfig { return routePlayersToPrimaryWorld; } + public String mainWorldPack() { + return mainWorldPack; + } + + public long mainWorldSeed() { + return mainWorldSeed; + } + + public boolean mainWorldAutoRestart() { + return mainWorldAutoRestart; + } + private static Path configFile() { return ModdedEngineBootstrap.loader().configDir().resolve("irisworldgen").resolve("modded.json"); } private static ModdedModConfig load() { Path file = configFile(); - ModdedModConfig defaults = new ModdedModConfig("overworld", true, "", true); + ModdedModConfig defaults = new ModdedModConfig("overworld", true, "", true, "", 0L, false); if (!Files.isRegularFile(file)) { write(file, defaults); return defaults; @@ -100,7 +130,10 @@ public final class ModdedModConfig { json.optString("defaultPack", defaults.defaultPack), json.optBoolean("autoDownloadDefaultPack", defaults.autoDownloadDefaultPack), json.optString("primaryWorld", defaults.primaryWorld), - json.optBoolean("routePlayersToPrimaryWorld", defaults.routePlayersToPrimaryWorld)); + json.optBoolean("routePlayersToPrimaryWorld", defaults.routePlayersToPrimaryWorld), + json.optString("mainWorldPack", defaults.mainWorldPack), + json.optLong("mainWorldSeed", defaults.mainWorldSeed), + json.optBoolean("mainWorldAutoRestart", defaults.mainWorldAutoRestart)); } catch (RuntimeException | IOException e) { LOGGER.error("Iris modded config at {} is invalid; using defaults", file, e); return defaults; @@ -113,6 +146,9 @@ public final class ModdedModConfig { json.put("autoDownloadDefaultPack", config.autoDownloadDefaultPack); json.put("primaryWorld", config.primaryWorld); json.put("routePlayersToPrimaryWorld", config.routePlayersToPrimaryWorld); + json.put("mainWorldPack", config.mainWorldPack); + json.put("mainWorldSeed", config.mainWorldSeed); + json.put("mainWorldAutoRestart", config.mainWorldAutoRestart); try { Files.createDirectories(file.getParent()); Files.writeString(file, json.toString(4), StandardCharsets.UTF_8); diff --git a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java index 21b8ecf1b..aba61006c 100644 --- a/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java +++ b/adapters/modded-common/src/main/java/art/arcane/iris/modded/command/ModdedWorldCommands.java @@ -23,6 +23,7 @@ import art.arcane.iris.core.pack.PackValidationRegistry; import art.arcane.iris.core.pack.PackValidationResult; import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.modded.IrisModdedChunkGenerator; +import art.arcane.iris.modded.MainWorldService; import art.arcane.iris.modded.ModdedDimensionManager; import art.arcane.iris.modded.ModdedEngineBootstrap; import art.arcane.iris.modded.ModdedModConfig; @@ -75,6 +76,7 @@ public final class ModdedWorldCommands { root.then(enableTree("enable")); root.then(enableTree("create")); root.then(replaceOverworldTree()); + root.then(mainWorldTree()); root.then(disableTree()); root.then(deleteTree("delete")); @@ -123,6 +125,20 @@ public final class ModdedWorldCommands { StringArgumentType.getString(context, "seed"))))); } + private static LiteralArgumentBuilder mainWorldTree() { + return Commands.literal("mainworld") + .then(Commands.literal("off") + .executes((CommandContext context) -> clearMainWorld(context.getSource()))) + .then(Commands.argument("pack", StringArgumentType.string()).suggests(IrisModdedCommands.PACK_NAMES) + .executes((CommandContext context) -> mainWorld(context.getSource(), + StringArgumentType.getString(context, "pack"), + null)) + .then(Commands.argument("seed", StringArgumentType.word()) + .executes((CommandContext context) -> mainWorld(context.getSource(), + StringArgumentType.getString(context, "pack"), + StringArgumentType.getString(context, "seed"))))); + } + public static int createWorld(CommandSourceStack source, String name, String pack, long seed) { String[] packRef = parsePackRef(pack); return enable(source, name, packRef[0], packRef[1], seed); @@ -223,6 +239,87 @@ public final class ModdedWorldCommands { return 1; } + private static int clearMainWorld(CommandSourceStack source) { + ModdedModConfig.setMainWorld("", 0L); + MainWorldService.clearOverride(); + IrisModdedCommands.ok(source, "Iris main world override cleared. The overworld keeps its current generator; edit server.properties level-type and restart to change it back."); + return 1; + } + + private static int mainWorld(CommandSourceStack source, String packRaw, String seedRaw) { + MinecraftServer server = source.getServer(); + long seed; + if (seedRaw == null || seedRaw.isBlank()) { + seed = 0L; + } else if (seedRaw.equalsIgnoreCase("random")) { + long rolled = ThreadLocalRandom.current().nextLong(); + seed = rolled == 0L ? 1L : rolled; + } else { + try { + seed = Long.parseLong(seedRaw.trim()); + } catch (NumberFormatException e) { + IrisModdedCommands.fail(source, "Invalid seed '" + seedRaw + "'. Use a number or 'random'."); + return 0; + } + } + String[] packRef = parsePackRef(packRaw); + String pack = packRef[0]; + String packDimension = packRef[1]; + if (!validPackRef(source, pack, packDimension)) { + return 0; + } + File packFolder = new File(ModdedPackCommands.packsRoot(), pack); + if (packFolder.isDirectory()) { + return applyMainWorld(source, pack, packDimension, packRaw, seed); + } + IrisModdedCommands.ok(source, "Pack '" + pack + "' is not installed; downloading IrisDimensions/" + pack + "..."); + Thread thread = new Thread(() -> { + boolean installed = ModdedPackInstaller.install(ModdedEngineBootstrap.loader().configDir(), pack, "master", + (String line) -> server.execute(() -> IrisModdedCommands.ok(source, line))); + server.execute(() -> { + if (!installed || !packFolder.isDirectory()) { + IrisModdedCommands.fail(source, "Pack '" + pack + "' could not be downloaded; check the name or install it with /iris download " + pack + "."); + return; + } + applyMainWorld(source, pack, packDimension, packRaw, seed); + }); + }, "Iris Main World Pack Download"); + thread.setDaemon(true); + thread.start(); + return 1; + } + + private static int applyMainWorld(CommandSourceStack source, String pack, String packDimension, String packRef, long seed) { + try { + if (!loadPackDimension(source, pack, packDimension)) { + return 0; + } + } catch (Throwable e) { + LOGGER.error("Iris main world pack load failed for {} (dim={})", pack, packDimension, e); + IrisModdedCommands.fail(source, "Pack '" + pack + "' is not ready yet (still loading or validating). Try the command again in a moment."); + return 0; + } + if (blockIfPackBroken(source, "the main world", pack)) { + return 0; + } + ModdedModConfig.setMainWorld(packRef, seed); + if (!MainWorldService.stage(packRef, seed)) { + IrisModdedCommands.fail(source, "Failed to write server.properties; check file permissions and set level-type manually."); + return 0; + } + String preset = MainWorldService.presetIdFor(packRef); + IrisModdedCommands.ok(source, "Iris main world set to '" + pack + "' (preset " + preset + ", seed " + (seed == 0L ? "random" : Long.toString(seed)) + ")."); + IrisModdedCommands.ok(source, "server.properties level-type is now " + preset + ". On the next restart the overworld, nether, and end regenerate as this Iris world."); + IrisModdedCommands.ok(source, "Player data (inventories, advancements, stats) is kept; existing terrain in those dimensions is replaced. This applies once."); + if (ModdedModConfig.get().mainWorldAutoRestart()) { + IrisModdedCommands.ok(source, "mainWorldAutoRestart is enabled - stopping the server now so your restart wrapper brings it back on the new main world."); + source.getServer().halt(false); + } else { + IrisModdedCommands.ok(source, "Restart the server now to generate it. (Set mainWorldAutoRestart=true in modded.json to have Iris stop the server for you.)"); + } + return 1; + } + private static boolean blockIfPackBroken(CommandSourceStack source, String dimensionId, String pack) { PackValidationResult validation = PackValidationRegistry.get(pack); if (validation == null || validation.isLoadable()) { diff --git a/docs/superpowers/2026-06-27-x86-simd-validation-handoff.md b/docs/superpowers/2026-06-27-x86-simd-validation-handoff.md deleted file mode 100644 index 811635eda..000000000 --- a/docs/superpowers/2026-06-27-x86-simd-validation-handoff.md +++ /dev/null @@ -1,145 +0,0 @@ -# x86 SIMD Validation Handoff — Iris 2D Simplex Noise Kernel - -**Paste this whole file to the Claude Code session on the x86 PC.** It is a self-contained task. The Mac that built this is Apple Silicon (NEON, 2 lanes, no hardware gather), where the new vectorized noise kernel is a measured *loss* and is deliberately gated OFF. This machine (x86 with AVX2 = 4 lanes or AVX-512 = 8 lanes, with hardware gather) is where the kernel is expected to *win*. Your job is to verify (a) it is still **bit-exact** at 4/8 lanes and (b) **how much faster** it actually is. - ---- - -## Background (what exists) - -A determinism-safe, coordinate-parallel (structure-of-arrays) `jdk.incubator.vector` kernel for 2D FBM simplex noise was added to Iris, with a scalar fallback: - -- `core/src/main/java/art/arcane/iris/util/simd/NoiseKernels2D.java` — interface -- `core/src/main/java/art/arcane/iris/util/simd/ScalarNoiseKernels2D.java` — scalar reference (mirrors `FastNoiseDouble`) -- `core/src/main/java/art/arcane/iris/util/simd/VectorNoiseKernels2D.java` — Vector-API impl; `profitable() = lanesAligned() && DoubleVector.SPECIES_PREFERRED.length() >= 4` -- `core/src/main/java/art/arcane/iris/util/simd/SimdSupport.java` — `noiseKernels2D()` selects vector only when `profitable()`, else scalar -- `core/src/test/java/art/arcane/iris/util/simd/NoiseKernels2DParityTest.java` — 6 bit-exact (`0D`) parity tests vs `FastNoiseDouble` - -Every lane computes one coordinate's identical scalar op sequence, so results are bit-identical to scalar by construction — **but this has only ever executed at 2 lanes.** You are the first to run it at 4/8. - -Java 25 is required. The Gradle test JVM already adds `--add-modules jdk.incubator.vector` (in `core/build.gradle`). - ---- - -## Your tasks (run from the Iris project root) - -### 1. Confirm the host actually vectorizes ≥4 lanes - -Run this throwaway check (or infer from step 2's skip count): - -```bash -cat > /tmp/Lanes.java <<'EOF' -import jdk.incubator.vector.DoubleVector; -public class Lanes { - public static void main(String[] a){ - System.out.println("arch="+System.getProperty("os.arch") - +" doubleLanes="+DoubleVector.SPECIES_PREFERRED.length() - +" shape="+DoubleVector.SPECIES_PREFERRED.vectorShape()); - } -} -EOF -java --add-modules jdk.incubator.vector /tmp/Lanes.java -``` - -Expect `doubleLanes=4` (AVX2) or `8` (AVX-512). If it prints `2`, this host is NOT a wider-vector machine and the rest of the validation won't be meaningful — report that and stop. - -### 2. Bit-exactness at 4/8 lanes (the determinism gate) - -```bash -./gradlew :core:test --tests 'art.arcane.iris.util.simd.NoiseKernels2DParityTest' --rerun-tasks -``` - -PASS criteria — read `core/build/test-results/test/TEST-art.arcane.iris.util.simd.NoiseKernels2DParityTest.xml`: -- `tests="6" failures="0" errors="0" skipped="0"` -- **`skipped="0"` is critical**: the vector parity tests are guarded by `assumeTrue(VectorNoiseKernels2D.lanesAligned())`. On this host they MUST run (not skip), exercising the vector path at this host's lane width. A skip means the vector path didn't execute — investigate. -- Any single non-zero delta is a determinism failure: the JDK's AVX2/AVX-512 `D2L`/`L2D`/mask-cast/gather intrinsics would have diverged from scalar. That is a hard blocker — capture the exact failing test, octave, index, expected vs actual. - -### 3. Measure the real speedup - -The benchmark harness was removed from the Mac (it was a measurement, not shipped). Re-create it here, run it, record the number, then delete it. - -Create `core/src/test/java/art/arcane/iris/util/simd/NoiseKernels2DBenchmarkHarness.java`: - -```java -package art.arcane.iris.util.simd; - -import art.arcane.volmlib.util.math.RNG; -import org.junit.Test; - -public class NoiseKernels2DBenchmarkHarness { - @Test - public void benchmark() { - if (!Boolean.getBoolean("noise.bench")) { - return; - } - int length = 256; - int octaves = 4; - double[] xs = new double[length]; - double[] zs = new double[length]; - double[] out = new double[length]; - RNG rng = new RNG(5L); - for (int k = 0; k < length; k++) { - xs[k] = (rng.nextDouble() - 0.5D) * 1_000_000D; - zs[k] = (rng.nextDouble() - 0.5D) * 1_000_000D; - } - NoiseKernels2D scalar = new ScalarNoiseKernels2D(); - NoiseKernels2D vector = new VectorNoiseKernels2D(); - long scalarNs = time(scalar, octaves, xs, zs, out, length); - long vectorNs = time(vector, octaves, xs, zs, out, length); - System.out.println("BENCH lanes=" + VectorNoiseKernels2D.profitable() - + " doubleLanes=" + jdk.incubator.vector.DoubleVector.SPECIES_PREFERRED.length() - + " scalarNsPerCol=" + scalarNs + " vectorNsPerCol=" + vectorNs - + " speedup=" + String.format("%.2f", (double) scalarNs / (double) vectorNs)); - } - - private static long time(NoiseKernels2D kernel, int octaves, double[] xs, double[] zs, double[] out, int length) { - for (int w = 0; w < 20_000; w++) { - kernel.simplexFractalFBM(123L, octaves, 0.01D, 2.0D, 0.5D, 0.6667D, xs, zs, out, length); - } - long start = System.nanoTime(); - int iters = 200_000; - for (int it = 0; it < iters; it++) { - kernel.simplexFractalFBM(123L, octaves, 0.01D, 2.0D, 0.5D, 0.6667D, xs, zs, out, length); - } - long elapsed = System.nanoTime() - start; - return elapsed / ((long) iters * (long) length); - } -} -``` - -Add this temporary block INSIDE the existing `tasks.named('test', Test) { ... }` in `core/build.gradle` (use the Edit tool; do not disturb the existing `jvmArgs('--add-modules', 'jdk.incubator.vector')` line): - -```groovy - if (project.hasProperty('noise.bench')) { - systemProperty('noise.bench', project.property('noise.bench')) - testLogging { showStandardStreams = true } - } -``` - -Run: - -```bash -./gradlew :core:test --tests 'art.arcane.iris.util.simd.NoiseKernels2DBenchmarkHarness' -Pnoise.bench=true -``` - -Capture the `BENCH ... speedup=...` line. - -Then CLEAN UP (do not commit either): delete the harness file and remove ONLY the `noise.bench` lines you added from `core/build.gradle` (edit them out by hand — do NOT `git checkout`/`git restore` the file, there may be other uncommitted work). Confirm with `git diff HEAD -- core/build.gradle` showing no diff. - ---- - -## Report back to the Mac (this exact info) - -1. `os.arch` and `doubleLanes` (4 or 8) from step 1. -2. Step 2 parity result: the `tests/failures/errors/skipped` counts. (Must be `6/0/0/0`.) If any failure: the failing test name, octave, index, expected vs actual. -3. Step 3 `BENCH` line: `scalarNsPerCol`, `vectorNsPerCol`, `speedup`. -4. Confirmation the benchmark harness was deleted and `core/build.gradle` restored to no-diff. - -That's it — do NOT wire the kernel into world generation, do NOT commit, do NOT touch any unrelated modified files in the working tree. This is a measurement-only task. - ---- - -## What the numbers decide (context for whoever relays this) - -- **Parity 6/0/0/0 with 0 skips** → the kernel is bit-exact at this lane width; the determinism gate holds on x86. Required before it can ever be wired into generation. -- **speedup > 1** (ideally ≥1.5–2× at 4 lanes, more at 8) → confirms the kernel is worth wiring into the live noise pipeline (Phase 2: batch the composite height/biome/cave noise paths, which is the deep part). Noise is ~33% of generation CPU, so a real per-eval win translates to a meaningful pregen/gen throughput gain. -- **speedup ≤ 1 even here** → the approach doesn't pay even with hardware gather + wide lanes; do not wire it; revisit a different optimization (e.g. the ~14% NMS chunk-write cost) or GPU compute. diff --git a/tools/simd-bench/README.md b/tools/simd-bench/README.md new file mode 100644 index 000000000..39d1304e7 --- /dev/null +++ b/tools/simd-bench/README.md @@ -0,0 +1,101 @@ +# Iris SIMD Kernel Benchmark + +Standalone, portable microbenchmark that measures whether Iris's SIMD (Java +Vector API) kernels actually beat their scalar equivalents **on the CPU it runs +on**. The Volmit dev/test machines are Apple Silicon, where the wide-SIMD path +(4+ double lanes) does not exist. Copy the built artifact to a Windows/x86 box +(AVX2 = 4 lanes, AVX-512 = 8 lanes) and run it to get real numbers for that CPU. + +The six kernel classes are copied verbatim (logic byte-for-byte) from +`Iris/core/.../util/simd/`. This tool is intentionally a duplicate so it builds +and runs on its own with no Gradle, no VolmLib, and no Iris on the classpath. + +## Requirements + +- **JDK 25** (the tool is compiled with `--release 25`). +- The JDK must ship the `jdk.incubator.vector` incubator module (Temurin, + Oracle, and all standard OpenJDK builds do). + +## How to run + +Windows: + +``` +run.bat +``` + +macOS / Linux: + +``` +./run.sh +``` + +Or invoke directly (the `--add-modules` flag is required because the Vector API +is still an incubator module): + +``` +java --add-modules jdk.incubator.vector -jar simd-bench.jar +``` + +### Mode flag + +By default it runs `both` (scalar and vector head-to-head in one run). You can +also do a two-run A/B: + +``` +run.bat --mode scalar +run.bat --mode vector +run.bat --mode both (default) +``` + +`--mode=scalar` syntax works too. The correctness cross-check only runs in +`both` mode (it needs both implementations). + +## How to read the output + +- **Header** prints the JVM, `os.arch`, CPU count, and the preferred vector + width. `DoubleVector pref: N lanes` is the SIMD width for `double` on this CPU + (2 on 128-bit NEON, 4 on AVX2, 8 on AVX-512). +- **noise SIMD gate (aligned && doubleLanes>=4)** mirrors the real profitability + gate in Iris's `VectorNoiseKernels2D`. It reports ENABLED on 4+ lane CPUs and + DISABLED on 2-lane NEON. **This tool ignores the gate and force-measures the + raw vector kernel anyway**, so you see the real number even where Iris would + gate SIMD off. +- **Correctness cross-check** runs each kernel once with both impls on identical + input and confirms they agree (roundToInt/noise are bit-exact; `sum` differs + only by floating-point reduction order, checked with a relative tolerance). + If anything says MISMATCH, do not trust the timing numbers below it. +- **speedup = scalar ns/op / vector ns/op.** `> 1.0` means SIMD is faster on + this CPU; `< 1.0` means SIMD is slower. Verdict column: SIMD FASTER / SIMD + SLOWER / NEUTRAL (within ~5%). +- `ns/op` for the array kernels is one full-array invocation (256 or 1024 + doubles). For noise it is one 256-element `simplexFractalFBM` call. +- The trailing **Checksum** lines exist only to keep the JIT from deleting the + measured work. Ignore their values. + +## Harness notes (why the numbers are trustworthy) + +- Each op is warmed up 50,000 times before timing so the JIT has compiled the + hot path. +- Every kernel output is folded into a running checksum (printed at the end) so + no measured call is dead-code-eliminated. +- One input element is perturbed per iteration (a cheap store keyed off the loop + counter) so the JIT cannot hoist a "constant" result out of the timing loop. + This perturbation is identical for scalar and vector, so the comparison stays + fair; it adds a tiny fixed cost to both sides that very slightly compresses the + ratio on the cheapest kernels. +- Each (kernel, impl) is timed over 10 rounds; the **minimum** ns/op is reported + (least-noisy statistic for a microbenchmark). +- Scalar and vector see the same seeded input for a given kernel. + +## Caveats + +- This is an **isolated-kernel vacuum microbench**, not full worldgen. It says + whether the raw kernel is faster on this CPU, not what end-to-end generation + throughput will be (cache behavior, allocation, and surrounding code differ in + the real engine). +- **Effort 1 array kernels** (`roundToInt` / `sum` / `max`) run unconditionally + in real Iris. **Effort 2 noise** (`simplexFractalFBM`) is currently unwired in + Iris and gated to 4+ double lanes; this tool force-measures it regardless. +- Vector-API auto-vectorization and cost depend heavily on the JDK version and + CPU. Run on the actual target hardware; do not extrapolate across machines. diff --git a/tools/simd-bench/out/simdbench/Bench.class b/tools/simd-bench/out/simdbench/Bench.class new file mode 100644 index 0000000000000000000000000000000000000000..45f8e6dff681a140a576cbde752875dc5d24e24e GIT binary patch literal 12112 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 UMP6U!*U^I;(tp8k@LN>)e|%c`D*ylh literal 0 HcmV?d00001 diff --git a/tools/simd-bench/out/simdbench/NoiseKernels2D.class b/tools/simd-bench/out/simdbench/NoiseKernels2D.class new file mode 100644 index 0000000000000000000000000000000000000000..e80d06ef3a69dec7bfd78c55ead616819b256ecc GIT binary patch literal 208 zcmY+8y9&ZU5Jm6!NPIMk7Jh;@25jvl2I8|2Y^*k$A+D^6Y@+yW7Jh&qC2nlQJKbeC z_q^VZCxAKHE-HjUuFi49sXTg{Oy#^}ld{eyzKa^6a}qbExne8$z?}(fRkJv!dJapLC1gJ%YMI)DHo2D%Vc;GdX1 zo?95sDezp#NrP>m8-n8aumUBgQXu~DV~<*#jNWM=gyjKNE?nwFlLn#$z{a=y{YP2W8RUcg=j!A7}OsuuI* zaPot!?&iA{0$V=J{r z)2TJ*oa)sgt@9gJb!=sciqPqDtvGXad9heO=PXvK(UyvhLcP35Hkuwdy}7%5v(8Z* zX3~7qDO9GNEA5EzGyM}o?anXcb73|f;!KZ^cG&B zrURbNF_j4j#dd=C;75?Av&oEZ>rr&}mEUi1L0fIHwqE8xicnb^tl)K9cU?ZP{5RBV zBZlzZM>u8-RfOg;>cyc-;Vb5B^_OG?a2#I5Sjpyax>;Wkzi}Kwg1aHuTf6a8VIa-G zQ)!_V?ju&o`qIKzSVKIc3AN4Xwr15UQ=WSW_6nymJ?Zp0NU% z$SMMXjHPEH!mzDdCB_x9QDNGVHg5|fREnZcCM;xyFt52eFn+@kJjBa*gk$&}qYCeZ62X`fM^4#}aV3Qb-DN6z+dQ4 z)pfjz*oIE(rA2;R)`^kme{HUj+IxdGfgr<%ba}H+|IAbH?(~Zej*MNEcmAA z9H?YNB1jreX1eSy5fb4w?9RqQ*=}_t9*g(f!vQKgq+3`nVbLx8m29_&*K zbQ4dpn{j1z{Z|nafh}h-OrQfZy!>a#okfTPNO0=C$nzx5aq{Om?Kw{OHCpnd^X~i- z@W_TuRgpc&kCWj`@t9|#aK=;Zb}k~!AF$UuV#Taj=pJ^)&6u^lSKcC_2|aGatf6?2 zWDWafZ(7{rL-{7Ppc3J;0xS?J7rC7U-q1z2JfFlT@G6gq5)G&RecA4kliv-ZHj3dj z90-*v4-g~Y5_!E2tvo=lWqt3T_eODKc3yR9M`(85=W>3RYLqRXBz{RWlO*6;EIv*Q mvpS{9kR&KQOi4oK>;r^1pHx%ghAo8n;`MQ-3Qkk@VEP}{eOC7X literal 0 HcmV?d00001 diff --git a/tools/simd-bench/out/simdbench/ScalarSimdKernels.class b/tools/simd-bench/out/simdbench/ScalarSimdKernels.class new file mode 100644 index 0000000000000000000000000000000000000000..556d95e297b7927e06890ada8c6cb831b0174e94 GIT binary patch literal 770 zcmZXSzi!h|6vn?B+i{wtAx+}6U^`apk~qk*5f9LT zH(+Dv!U#+t1|EPnK`fmsO8Ksdk^&yiIrrXkf8TeH{O#A5uK*sRkwyZF4mAr6NrC*a z_0npMEdQ|8w~t*X6iD9ld@sB&pft_>6jI!oI@ZVv(nuqtBb&uCass)<)m<}Aa1aKbe|T3Q9h=;nbbUTG)ja4N zbi3xh!15rpoaejNiy29oMq|F54PCRtS&Xe$ygZmhf#W{%q*nPq&v&FA+9-0yG|y*h z%$zghDpz%8A$K=FL40H(u*N8l0fq58DvYwV${g<&^bh|u%JwR1v&KGDDgZTQTQ$`B zDU92Ox@D;C&scihKT*ZI%O6rI5la==4P>x{Ed6E3$v6ZOzbXr^uUIhSqj^CmP9T=KDFi9NQ>Rs7P=L1?DyKB%VLr1vJSokbHB_e>USc zN?4gItV#j%&0C~o@ki+U6`@LOsm7~y{)p=+6IW%ufkrHrfpP&|5ykU({2Q^uTfYIi CwTDLl literal 0 HcmV?d00001 diff --git a/tools/simd-bench/out/simdbench/SimdKernels.class b/tools/simd-bench/out/simdbench/SimdKernels.class new file mode 100644 index 0000000000000000000000000000000000000000..7b1832343af4d50e962a34213af65bf3d129028c GIT binary patch literal 224 zcmYk0Jr06E5QX3P1OA6X?;wpFz*bF&CKebQs%6>W5?GQ&jAyg(03OOX*l08F&E(6Q z_j*5`0JfO9=nxjQF4IIAc?lwRdu5F(Y8O3%my4SS3SqL~nB+=c34>JC(&|JJ=1c$Z z1EQ!u_)dpP&8ZQ%?gJ76S_E1h!@3OLMbEGPckijp+$JJ)VkZjGK*g(nw_X z54mASy#j{{1x^9qDgCtGozRo>-4nB?j96B{aWI~YXAcS3+S-r1;D$$ms-gjnWx$Vf zbLt}C0?I%nd~|$xNI={!&-zp}$%F1=lM^H1sevPrVL9ER!jFJJ-B46uXXMn}Vs|_l zTbb1fvir0Nx!XbzT{dEIBh&A}We6(JR9ucN0$0EI5pt3+lJnUGlasInuvNu2Y!~oY zD54AcvO)Z9?Nup|yZYU@3Rf%Gp<*YlDU(|?n~o>v$@*zMv0_Y|6xh`kxmc|IM6n@h z>qLJuwYFPD8`=dfThbSe!BjG)XI~!AF0hx$>d9~v??_Pf!2b*52Gq>1`W%WS(z0WZX2zpE0gL9aEn@?$#q~{#e}qO zTSP{(K3p$T#o~H4 ze%k1-r>FDJK|krkD^$D^cM#grN?4qU28}v`VOj8><#LCtTV86+r zG&3rB{w)=6z#HiY@nkkNkWTAonF8BpM#EMbf0K$g<1L&zIyo?Og63Q|Lm%qF+f=+A zzs+MO)2XHWv5Bf;E5w4Cq3}$&QiLsy_GY^H9TmS@#6K-jzf)S#y9AC@MOVp+$}mW9 zzo+8e5}Zw+n{(XwEz{?ylpg%Pj1YeyFj^H)fvrk0_7Z#_kWubk62XU5oWq9&>Y3n@ zb263=RkPh{5w8r3w5f+wd=w8;T_gK>t1?yghf;2j%8N#gw7T(_ia)|1^XhXlkxHef zQqr*nMk)xTjEENx4bu7^m%^WSli*bqYxomJs%*-FQyEW5c&UJ;j1no1oQfw*aU9(* zUGYg3Pnn7N=#*5%d6{HCEpS%_#x9N>gJv!^b96O@RWiY18l^Ay;WH|p!Dku2?lID- z|7S)m5sd`>d1+x0$>JBKpdXdJKa=+NsN=9@=#-5cUsmxI{5hR@KCRCUQqwfX$1A1Z z5)_*=|Dqepw6i&kvMn<;MGMKuU#j>j{)*8mmSW|?NdEQ;8ZCog0|_;3sl^nfPX0z} z;tt2*a3RvT@pTn{i@#&Nlhxx1tNs#bY8wp?@o&cbFk&8;#mF~Q`~&`xs-i;xV8+h5V07DYopnXe^&7?_*a3J42zb85sfb`CyX-|IDup( zQ+cUUu2s=J_>Rn4-!(H8xj62I!1q=B06%2fWGO4D8%gW2tezM-GFFhNCoj>Vq4psc z#q}cDXnUX6Uq*68# zc1cEO*8aIbuvn;Oa&^>R#ee>F(x1|5$jO zA}mzwCn5vm!z|QASfHU$njV+cu8m@~v!uC=yTfC{+%n41bYZlmgsu|)56+BA^6Z1D zIWp*t#FNJO%F?Wnp3<2=d3%yq+{{3NLNMqQe22K59<`iKos4I#9nLu;6HCWuxw-sW zlrODGXEdwF7RU5u6Q&}X=EauDov;DXt?kebUl#T6t{oaybk z(&V=DO5wx3UWeGu=qIjJML@LrL@OtDxW!I!jUsmOsX*+OmHmcI#gnH~i$=G_G!?_w zgAuw)D$SeUyYr@LzU|~UTDWi0lXD3pvon%PEv_uHEZBgTmtM(C*}FOAS8{Vo*;cqVcTJPnO!5zmY@c99 z2p4}H=A;WQ;TE3!)+@|8M&ebt9=m8~kUQ1Qc6u+~NhqR0H0ELMqevurZ57Q=qE*0b z8MD@LS)DYycXV6nTY>B3Whj<0H4vBu$Q?9^yBHXr) zTUK#vrytWf%;a!_gDyW})*z&4{N!MmKYlE5TF$NFlpo0)mP1a>$rEzR1$uYLxv54km$<|bwL*?K(zkLGy#9Q>MtJ$JyfIjl34e~5ws_R{+HVGFHw2d?9TYY!j%uE#O-vM4-& z1>AsTUV2`I5HD7JcqE-Hy5o$iQP)&}V*o6!a*^Xd} zsIOM)1~@`L804e+5R18C@-jlSyrJgx_aIzorR|403G%^Jd?bfQLiL>z?t|<2Sjf?} z(N#2u)Xm4&@rhM@QtoqhY7UMxXAbY@H{|Nll+rPkwTgAkox`U>9*ugCHW^acuH$L9 z6#wT!4IH^?I4*10Nc}bt2Emy~z7X2|RjGt^GAXvyp7Irwk|AwSRX^IlxOeQVGjbOfD2N>FRi8f^jZI=)7o zSaGD+$6mnuB>uh*y-fk%d3UJU*4q+j2{fI><&|#j^0#+vw)Pl~%K1~a@wz~CK94HI4tL{+ zIAi*38ueQ*Fd%vrxb4EmKSlVhuMmRHONYIHLkLt{mp+aj`h1Vgbne3!NOm=F^gjH+v5Dl*Y+vr#bRD*!3yi$u-vOIdaOKa_HmnHN0aL|FMRTtl~e{ z@X4p}Utli%=^D-y1+|OO4wSruqEh+U%}77Mn7>R9`v5iY5Iy)~taD`?cf!8oum~Pz zeI$aWs5Nmp+hz*=YD#j8XyeLOu}xehx;eiG^sxomjD0& literal 0 HcmV?d00001 diff --git a/tools/simd-bench/out/simdbench/VectorSimdKernels.class b/tools/simd-bench/out/simdbench/VectorSimdKernels.class new file mode 100644 index 0000000000000000000000000000000000000000..711cc9c80cb2a41e414dfe5a3a14252b0078f536 GIT binary patch literal 3964 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 R6B-6+<~LZ7&G~2ae*kDY doubleSpecies = DoubleVector.SPECIES_PREFERRED; + VectorSpecies longSpecies = LongVector.SPECIES_PREFERRED; + int doubleLanes = doubleSpecies.length(); + int longLanes = longSpecies.length(); + boolean aligned = doubleLanes == longLanes; + boolean gate = aligned && doubleLanes >= 4; + System.out.println("=== Iris SIMD Kernel Benchmark ==="); + System.out.println("Java version: " + System.getProperty("java.version")); + System.out.println("Java vendor: " + System.getProperty("java.vendor")); + System.out.println("os.arch: " + System.getProperty("os.arch")); + System.out.println("os.name: " + System.getProperty("os.name")); + System.out.println("availableProcessors: " + Runtime.getRuntime().availableProcessors()); + System.out.println("DoubleVector pref: " + doubleLanes + " lanes, " + doubleSpecies.vectorShape()); + System.out.println("LongVector pref: " + longLanes + " lanes, " + longSpecies.vectorShape()); + System.out.println("noise SIMD gate (aligned && doubleLanes>=4): " + (gate ? "ENABLED" : "DISABLED") + " on this CPU"); + System.out.println("Mode: " + mode + " (WARMUP=" + WARMUP + ", ROUNDS=" + ROUNDS + ", reporting MIN ns/op)"); + } + + private static void runCorrectness(SimdKernels scalarArray, SimdKernels vectorArray, + NoiseKernels2D scalarNoise, NoiseKernels2D vectorNoise) { + System.out.println(); + System.out.println("--- Correctness cross-check (scalar vs vector on identical input) ---"); + for (int li = 0; li < ARRAY_LENGTHS.length; li++) { + int length = ARRAY_LENGTHS[li]; + double[] data = makeArray(length, 777L); + + int[] targetScalar = new int[length]; + int[] targetVector = new int[length]; + scalarArray.roundToInt(data, targetScalar, length); + vectorArray.roundToInt(data, targetVector, length); + int mismatches = 0; + for (int i = 0; i < length; i++) { + if (targetScalar[i] != targetVector[i]) { + mismatches++; + } + } + System.out.printf(" roundToInt len=%-5d %s%n", length, + mismatches == 0 ? "MATCH" : ("MISMATCH x" + mismatches)); + + double sumScalar = scalarArray.sum(data, length); + double sumVector = vectorArray.sum(data, length); + System.out.printf(" sum len=%-5d %s (scalar=%.6f vector=%.6f, |rel diff|=%.2e)%n", + length, relClose(sumScalar, sumVector, 1.0E-9D) ? "MATCH" : "MISMATCH", + sumScalar, sumVector, relDiff(sumScalar, sumVector)); + + double maxScalar = scalarArray.max(data, length); + double maxVector = vectorArray.max(data, length); + System.out.printf(" max len=%-5d %s (scalar=%.6f vector=%.6f)%n", + length, maxScalar == maxVector ? "MATCH" : "MISMATCH", maxScalar, maxVector); + } + + double[] xs = makeCoords(NOISE_LENGTH, 0.0D, 1.0D); + double[] zs = makeCoords(NOISE_LENGTH, 4096.0D, 1.0D); + for (int oi = 0; oi < OCTAVE_SET.length; oi++) { + int octaves = OCTAVE_SET[oi]; + double fractalBounding = calcFractalBounding(octaves, GAIN); + double[] outScalar = new double[NOISE_LENGTH]; + double[] outVector = new double[NOISE_LENGTH]; + scalarNoise.simplexFractalFBM(SEED, octaves, FREQUENCY, LACUNARITY, GAIN, fractalBounding, xs, zs, outScalar, NOISE_LENGTH); + vectorNoise.simplexFractalFBM(SEED, octaves, FREQUENCY, LACUNARITY, GAIN, fractalBounding, xs, zs, outVector, NOISE_LENGTH); + double maxAbs = 0.0D; + for (int i = 0; i < NOISE_LENGTH; i++) { + double diff = Math.abs(outScalar[i] - outVector[i]); + if (diff > maxAbs) { + maxAbs = diff; + } + } + System.out.printf(" noise oct=%-2d %s (max |diff|=%.2e)%n", + octaves, maxAbs < 1.0E-9D ? "MATCH" : "MISMATCH", maxAbs); + } + } + + private static void runArrayBenchmarks(String mode, SimdKernels scalar, SimdKernels vector) { + System.out.println(); + System.out.println("--- Array kernels (one op = one full-array invocation, batch=" + ARRAY_BATCH + ") ---"); + System.out.printf("%-12s %-6s %14s %14s %9s %s%n", + "kernel", "len", "scalar ns/op", "vector ns/op", "speedup", "verdict"); + String[] kernels = {"roundToInt", "sum", "max"}; + for (int li = 0; li < ARRAY_LENGTHS.length; li++) { + int length = ARRAY_LENGTHS[li]; + for (int ki = 0; ki < kernels.length; ki++) { + String kernel = kernels[ki]; + double scalarNs = Double.NaN; + double vectorNs = Double.NaN; + if (!mode.equals("vector")) { + scalarNs = timeArrayKernel(kernel, scalar, length); + } + if (!mode.equals("scalar")) { + vectorNs = timeArrayKernel(kernel, vector, length); + } + printRow(kernel, Integer.toString(length), scalarNs, vectorNs); + } + } + } + + private static void runNoiseBenchmarks(String mode, NoiseKernels2D scalar, NoiseKernels2D vector) { + System.out.println(); + System.out.println("--- Noise kernel simplexFractalFBM (one op = one 256-element invocation) ---"); + System.out.printf("%-10s %-8s %14s %14s %9s %s%n", + "octaves", "batch", "scalar ns/op", "vector ns/op", "speedup", "verdict"); + int mask = NOISE_LENGTH - 1; + for (int oi = 0; oi < OCTAVE_SET.length; oi++) { + int octaves = OCTAVE_SET[oi]; + int batch = noiseBatch(octaves); + double scalarNs = Double.NaN; + double vectorNs = Double.NaN; + if (!mode.equals("vector")) { + scalarNs = timeNoise(scalar, octaves, mask, batch); + } + if (!mode.equals("scalar")) { + vectorNs = timeNoise(vector, octaves, mask, batch); + } + printNoiseRow(octaves, batch, scalarNs, vectorNs); + } + } + + private static double timeArrayKernel(String kernel, SimdKernels impl, int length) { + double[] source = makeArray(length, 20260701L); + int[] target = new int[length]; + int mask = length - 1; + return switch (kernel) { + case "roundToInt" -> timeRoundToInt(impl, source, target, length, mask); + case "sum" -> timeSum(impl, source, length, mask); + case "max" -> timeMax(impl, source, length, mask); + default -> throw new IllegalArgumentException(kernel); + }; + } + + private static double timeRoundToInt(SimdKernels impl, double[] source, int[] target, int length, int mask) { + long warm = 0L; + for (int b = 0; b < WARMUP; b++) { + source[b & mask] = perturb(b); + impl.roundToInt(source, target, length); + warm += target[b & mask]; + } + longSink += warm; + double best = Double.MAX_VALUE; + for (int r = 0; r < ROUNDS; r++) { + long localSink = 0L; + long start = System.nanoTime(); + for (int b = 0; b < ARRAY_BATCH; b++) { + source[b & mask] = perturb(b); + impl.roundToInt(source, target, length); + localSink += target[b & mask]; + } + long elapsed = System.nanoTime() - start; + longSink += localSink; + double nsPerOp = (double) elapsed / (double) ARRAY_BATCH; + if (nsPerOp < best) { + best = nsPerOp; + } + } + return best; + } + + private static double timeSum(SimdKernels impl, double[] source, int length, int mask) { + double warm = 0.0D; + for (int b = 0; b < WARMUP; b++) { + source[b & mask] = perturb(b); + warm += impl.sum(source, length); + } + doubleSink += warm; + double best = Double.MAX_VALUE; + for (int r = 0; r < ROUNDS; r++) { + double localSink = 0.0D; + long start = System.nanoTime(); + for (int b = 0; b < ARRAY_BATCH; b++) { + source[b & mask] = perturb(b); + localSink += impl.sum(source, length); + } + long elapsed = System.nanoTime() - start; + doubleSink += localSink; + double nsPerOp = (double) elapsed / (double) ARRAY_BATCH; + if (nsPerOp < best) { + best = nsPerOp; + } + } + return best; + } + + private static double timeMax(SimdKernels impl, double[] source, int length, int mask) { + double warm = 0.0D; + for (int b = 0; b < WARMUP; b++) { + source[b & mask] = perturb(b); + warm += impl.max(source, length); + } + doubleSink += warm; + double best = Double.MAX_VALUE; + for (int r = 0; r < ROUNDS; r++) { + double localSink = 0.0D; + long start = System.nanoTime(); + for (int b = 0; b < ARRAY_BATCH; b++) { + source[b & mask] = perturb(b); + localSink += impl.max(source, length); + } + long elapsed = System.nanoTime() - start; + doubleSink += localSink; + double nsPerOp = (double) elapsed / (double) ARRAY_BATCH; + if (nsPerOp < best) { + best = nsPerOp; + } + } + return best; + } + + private static double timeNoise(NoiseKernels2D impl, int octaves, int mask, int batch) { + double[] xs = makeCoords(NOISE_LENGTH, 0.0D, 1.0D); + double[] zs = makeCoords(NOISE_LENGTH, 4096.0D, 1.0D); + double[] out = new double[NOISE_LENGTH]; + double fractalBounding = calcFractalBounding(octaves, GAIN); + double warm = 0.0D; + for (int b = 0; b < WARMUP; b++) { + xs[b & mask] = 0.5D * (double) (b & 1023); + impl.simplexFractalFBM(SEED, octaves, FREQUENCY, LACUNARITY, GAIN, fractalBounding, xs, zs, out, NOISE_LENGTH); + warm += out[b & mask]; + } + doubleSink += warm; + double best = Double.MAX_VALUE; + for (int r = 0; r < ROUNDS; r++) { + double localSink = 0.0D; + long start = System.nanoTime(); + for (int b = 0; b < batch; b++) { + xs[b & mask] = 0.5D * (double) (b & 1023); + impl.simplexFractalFBM(SEED, octaves, FREQUENCY, LACUNARITY, GAIN, fractalBounding, xs, zs, out, NOISE_LENGTH); + localSink += out[b & mask]; + } + long elapsed = System.nanoTime() - start; + doubleSink += localSink; + double nsPerOp = (double) elapsed / (double) batch; + if (nsPerOp < best) { + best = nsPerOp; + } + } + return best; + } + + private static double perturb(int b) { + return (double) ((b * 2654435761L) & 1023L) - 256.0D; + } + + private static int noiseBatch(int octaves) { + return Math.max(4_000, 50_000 / octaves); + } + + private static double calcFractalBounding(int octaves, double gain) { + double amp = gain; + double ampFractal = 1.0D; + for (int i = 1; i < octaves; i++) { + ampFractal += amp; + amp *= gain; + } + return 1.0D / ampFractal; + } + + private static double[] makeArray(int length, long seed) { + Random random = new Random(seed); + double[] data = new double[length]; + for (int i = 0; i < length; i++) { + data[i] = random.nextDouble() * 512.0D - 64.0D; + } + return data; + } + + private static double[] makeCoords(int length, double origin, double step) { + double[] data = new double[length]; + for (int i = 0; i < length; i++) { + data[i] = origin + (double) i * step; + } + return data; + } + + private static void printRow(String kernel, String length, double scalarNs, double vectorNs) { + String scalarText = Double.isNaN(scalarNs) ? "-" : String.format(Locale.ROOT, "%.3f", scalarNs); + String vectorText = Double.isNaN(vectorNs) ? "-" : String.format(Locale.ROOT, "%.3f", vectorNs); + String speedupText = "-"; + String verdict = "-"; + if (!Double.isNaN(scalarNs) && !Double.isNaN(vectorNs) && vectorNs > 0.0D) { + double speedup = scalarNs / vectorNs; + speedupText = String.format(Locale.ROOT, "%.2fx", speedup); + verdict = verdictFor(speedup); + } + System.out.printf("%-12s %-6s %14s %14s %9s %s%n", kernel, length, scalarText, vectorText, speedupText, verdict); + } + + private static void printNoiseRow(int octaves, int batch, double scalarNs, double vectorNs) { + String scalarText = Double.isNaN(scalarNs) ? "-" : String.format(Locale.ROOT, "%.1f", scalarNs); + String vectorText = Double.isNaN(vectorNs) ? "-" : String.format(Locale.ROOT, "%.1f", vectorNs); + String speedupText = "-"; + String verdict = "-"; + if (!Double.isNaN(scalarNs) && !Double.isNaN(vectorNs) && vectorNs > 0.0D) { + double speedup = scalarNs / vectorNs; + speedupText = String.format(Locale.ROOT, "%.2fx", speedup); + verdict = verdictFor(speedup); + } + System.out.printf("%-10d %-8d %14s %14s %9s %s%n", octaves, batch, scalarText, vectorText, speedupText, verdict); + } + + private static String verdictFor(double speedup) { + if (speedup > 1.05D) { + return "SIMD FASTER"; + } + if (speedup < 0.95D) { + return "SIMD SLOWER"; + } + return "NEUTRAL"; + } + + private static double relDiff(double a, double b) { + double denom = Math.max(Math.abs(a), Math.abs(b)); + if (denom == 0.0D) { + return 0.0D; + } + return Math.abs(a - b) / denom; + } + + private static boolean relClose(double a, double b, double tol) { + return relDiff(a, b) <= tol; + } +} diff --git a/tools/simd-bench/src/simdbench/NoiseKernels2D.java b/tools/simd-bench/src/simdbench/NoiseKernels2D.java new file mode 100644 index 000000000..9ae6320cc --- /dev/null +++ b/tools/simd-bench/src/simdbench/NoiseKernels2D.java @@ -0,0 +1,8 @@ +package simdbench; + +public interface NoiseKernels2D { + String describe(); + + void simplexFractalFBM(long seed, int octaves, double frequency, double lacunarity, double gain, + double fractalBounding, double[] xs, double[] zs, double[] out, int length); +} diff --git a/tools/simd-bench/src/simdbench/ScalarNoiseKernels2D.java b/tools/simd-bench/src/simdbench/ScalarNoiseKernels2D.java new file mode 100644 index 000000000..f02a21e1e --- /dev/null +++ b/tools/simd-bench/src/simdbench/ScalarNoiseKernels2D.java @@ -0,0 +1,99 @@ +package simdbench; + +public final class ScalarNoiseKernels2D implements NoiseKernels2D { + static final double F2 = 0.5D * (Math.sqrt(3.0D) - 1.0D); + static final double G2 = (3.0D - Math.sqrt(3.0D)) / 6.0D; + static final long X_PRIME = 1619L; + static final long Y_PRIME = 31337L; + static final double[] GRAD_2D = {-1D, -1D, 1D, -1D, -1D, 1D, 1D, 1D, 0D, -1D, -1D, 0D, 0D, 1D, 1D, 0D}; + + @Override + public String describe() { + return "scalar"; + } + + static long fastFloor(double f) { + return f >= 0D ? (long) f : (long) f - 1L; + } + + static double gradCoord2D(long seed, long x, long y, double xd, double yd) { + long hash = seed; + hash ^= X_PRIME * x; + hash ^= Y_PRIME * y; + hash = hash * hash * hash * 60493L; + hash = (hash >> 13) ^ hash; + int gradientIndex = ((int) hash & 7) << 1; + return (xd * GRAD_2D[gradientIndex]) + (yd * GRAD_2D[gradientIndex + 1]); + } + + static double singleSimplex(long seed, double x, double y) { + double t = (x + y) * F2; + long i = fastFloor(x + t); + long j = fastFloor(y + t); + t = (i + j) * G2; + double x0 = x - (i - t); + double y0 = y - (j - t); + long i1; + long j1; + if (x0 > y0) { + i1 = 1L; + j1 = 0L; + } else { + i1 = 0L; + j1 = 1L; + } + double x1 = x0 - i1 + G2; + double y1 = y0 - j1 + G2; + double x2 = x0 - 1D + (2D * G2); + double y2 = y0 - 1D + (2D * G2); + double n0; + double n1; + double n2; + double a = 0.5D - x0 * x0 - y0 * y0; + if (a < 0D) { + n0 = 0D; + } else { + a *= a; + n0 = a * a * gradCoord2D(seed, i, j, x0, y0); + } + double b = 0.5D - x1 * x1 - y1 * y1; + if (b < 0D) { + n1 = 0D; + } else { + b *= b; + n1 = b * b * gradCoord2D(seed, i + i1, j + j1, x1, y1); + } + double c = 0.5D - x2 * x2 - y2 * y2; + if (c < 0D) { + n2 = 0D; + } else { + c *= c; + n2 = c * c * gradCoord2D(seed, i + 1L, j + 1L, x2, y2); + } + return 50D * (n0 + n1 + n2); + } + + static double simplexFractalFBMScalar(long seed, int octaves, double frequency, double lacunarity, double gain, + double fractalBounding, double xIn, double yIn) { + double x = xIn * frequency; + double y = yIn * frequency; + long s = seed; + double sum = singleSimplex(s, x, y); + double amp = 1D; + for (int o = 1; o < octaves; o++) { + x *= lacunarity; + y *= lacunarity; + amp *= gain; + sum += singleSimplex(++s, x, y) * amp; + } + return sum * fractalBounding; + } + + @Override + public void simplexFractalFBM(long seed, int octaves, double frequency, double lacunarity, double gain, + double fractalBounding, double[] xs, double[] zs, double[] out, int length) { + for (int k = 0; k < length; k++) { + out[k] = simplexFractalFBMScalar(seed, octaves, frequency, lacunarity, gain, fractalBounding, xs[k], zs[k]); + } + } +} diff --git a/tools/simd-bench/src/simdbench/ScalarSimdKernels.java b/tools/simd-bench/src/simdbench/ScalarSimdKernels.java new file mode 100644 index 000000000..ef577e051 --- /dev/null +++ b/tools/simd-bench/src/simdbench/ScalarSimdKernels.java @@ -0,0 +1,37 @@ +package simdbench; + +public final class ScalarSimdKernels implements SimdKernels { + @Override + public String describe() { + return "scalar"; + } + + @Override + public void roundToInt(double[] source, int[] target, int length) { + for (int index = 0; index < length; index++) { + target[index] = (int) Math.round(source[index]); + } + } + + @Override + public double sum(double[] values, int length) { + double total = 0D; + for (int index = 0; index < length; index++) { + total += values[index]; + } + + return total; + } + + @Override + public double max(double[] values, int length) { + double best = Double.NEGATIVE_INFINITY; + for (int index = 0; index < length; index++) { + if (values[index] > best) { + best = values[index]; + } + } + + return best; + } +} diff --git a/tools/simd-bench/src/simdbench/SimdKernels.java b/tools/simd-bench/src/simdbench/SimdKernels.java new file mode 100644 index 000000000..f73d6fd00 --- /dev/null +++ b/tools/simd-bench/src/simdbench/SimdKernels.java @@ -0,0 +1,11 @@ +package simdbench; + +public interface SimdKernels { + String describe(); + + void roundToInt(double[] source, int[] target, int length); + + double sum(double[] values, int length); + + double max(double[] values, int length); +} diff --git a/tools/simd-bench/src/simdbench/VectorNoiseKernels2D.java b/tools/simd-bench/src/simdbench/VectorNoiseKernels2D.java new file mode 100644 index 000000000..fae195856 --- /dev/null +++ b/tools/simd-bench/src/simdbench/VectorNoiseKernels2D.java @@ -0,0 +1,129 @@ +package simdbench; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.LongVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +public final class VectorNoiseKernels2D implements NoiseKernels2D { + private static final VectorSpecies DS = DoubleVector.SPECIES_PREFERRED; + private static final VectorSpecies LS = LongVector.SPECIES_PREFERRED; + private static final boolean ALIGNED = DS.length() == LS.length(); + private static final int MIN_PROFITABLE_LANES = 4; + private static final boolean PROFITABLE = ALIGNED && DS.length() >= MIN_PROFITABLE_LANES; + private static final double[] GRAD_X = {-1D, 1D, -1D, 1D, 0D, -1D, 0D, 1D}; + private static final double[] GRAD_Y = {-1D, -1D, 1D, 1D, -1D, 0D, 1D, 0D}; + private static final double F2 = ScalarNoiseKernels2D.F2; + private static final double G2 = ScalarNoiseKernels2D.G2; + private static final long X_PRIME = ScalarNoiseKernels2D.X_PRIME; + private static final long Y_PRIME = ScalarNoiseKernels2D.Y_PRIME; + private static final ThreadLocal LONG_SCRATCH = ThreadLocal.withInitial(() -> new long[LS.length()]); + + public static boolean lanesAligned() { + return ALIGNED; + } + + public static boolean profitable() { + return PROFITABLE; + } + + @Override + public String describe() { + return DS.length() + "x64 lanes, " + DS.vectorShape(); + } + + private static LongVector floorToLong(DoubleVector f) { + LongVector truncated = (LongVector) f.convertShape(VectorOperators.D2L, LS, 0); + VectorMask negative = f.compare(VectorOperators.LT, 0D).cast(LS); + return truncated.sub(1L, negative); + } + + private static DoubleVector toDouble(LongVector v) { + return (DoubleVector) v.convertShape(VectorOperators.L2D, DS, 0); + } + + private static DoubleVector gradCoord(long seed, LongVector i, LongVector j, DoubleVector xd, DoubleVector yd, + int[] idxScratch) { + LongVector hash = LongVector.broadcast(LS, seed) + .lanewise(VectorOperators.XOR, i.mul(X_PRIME)) + .lanewise(VectorOperators.XOR, j.mul(Y_PRIME)); + hash = hash.mul(hash).mul(hash).mul(60493L); + LongVector shifted = hash.lanewise(VectorOperators.ASHR, 13); + hash = shifted.lanewise(VectorOperators.XOR, hash); + LongVector idx = hash.lanewise(VectorOperators.AND, 7L); + long[] tmp = LONG_SCRATCH.get(); + idx.intoArray(tmp, 0); + for (int l = 0; l < idxScratch.length; l++) { + idxScratch[l] = (int) tmp[l]; + } + DoubleVector gx = DoubleVector.fromArray(DS, GRAD_X, 0, idxScratch, 0); + DoubleVector gy = DoubleVector.fromArray(DS, GRAD_Y, 0, idxScratch, 0); + return xd.mul(gx).add(yd.mul(gy)); + } + + private static DoubleVector corner(DoubleVector xk, DoubleVector yk, DoubleVector grad) { + DoubleVector t = DoubleVector.broadcast(DS, 0.5D).sub(xk.mul(xk)).sub(yk.mul(yk)); + VectorMask negative = t.compare(VectorOperators.LT, 0D); + DoubleVector t2 = t.mul(t); + DoubleVector t4 = t2.mul(t2); + return t4.mul(grad).blend(0D, negative); + } + + private static DoubleVector singleSimplexVector(long seed, DoubleVector x, DoubleVector y, int[] idxScratch) { + DoubleVector t = x.add(y).mul(F2); + LongVector i = floorToLong(x.add(t)); + LongVector j = floorToLong(y.add(t)); + DoubleVector skew = toDouble(i.add(j)).mul(G2); + DoubleVector x0 = x.sub(toDouble(i).sub(skew)); + DoubleVector y0 = y.sub(toDouble(j).sub(skew)); + VectorMask xGreater = x0.compare(VectorOperators.GT, y0); + VectorMask xGreaterL = xGreater.cast(LS); + LongVector i1 = LongVector.zero(LS).blend(1L, xGreaterL); + LongVector j1 = LongVector.broadcast(LS, 1L).blend(0L, xGreaterL); + DoubleVector x1 = x0.sub(toDouble(i1)).add(G2); + DoubleVector y1 = y0.sub(toDouble(j1)).add(G2); + DoubleVector x2 = x0.sub(1D).add(2D * G2); + DoubleVector y2 = y0.sub(1D).add(2D * G2); + DoubleVector n0 = corner(x0, y0, gradCoord(seed, i, j, x0, y0, idxScratch)); + DoubleVector n1 = corner(x1, y1, gradCoord(seed, i.add(i1), j.add(j1), x1, y1, idxScratch)); + DoubleVector n2 = corner(x2, y2, gradCoord(seed, i.add(1L), j.add(1L), x2, y2, idxScratch)); + return n0.add(n1).add(n2).mul(50D); + } + + @Override + public void simplexFractalFBM(long seed, int octaves, double frequency, double lacunarity, double gain, + double fractalBounding, double[] xs, double[] zs, double[] out, int length) { + if (!ALIGNED) { + tailScalar(seed, octaves, frequency, lacunarity, gain, fractalBounding, xs, zs, out, 0, length); + return; + } + int lanes = DS.length(); + int bound = DS.loopBound(length); + int[] idxScratch = new int[lanes]; + int k = 0; + for (; k < bound; k += lanes) { + DoubleVector x = DoubleVector.fromArray(DS, xs, k).mul(frequency); + DoubleVector y = DoubleVector.fromArray(DS, zs, k).mul(frequency); + long s = seed; + DoubleVector sum = singleSimplexVector(s, x, y, idxScratch); + double amp = 1D; + for (int o = 1; o < octaves; o++) { + x = x.mul(lacunarity); + y = y.mul(lacunarity); + amp *= gain; + sum = sum.add(singleSimplexVector(++s, x, y, idxScratch).mul(amp)); + } + sum.mul(fractalBounding).intoArray(out, k); + } + tailScalar(seed, octaves, frequency, lacunarity, gain, fractalBounding, xs, zs, out, k, length); + } + + private static void tailScalar(long seed, int octaves, double frequency, double lacunarity, double gain, + double fractalBounding, double[] xs, double[] zs, double[] out, int from, int length) { + for (int k = from; k < length; k++) { + out[k] = ScalarNoiseKernels2D.simplexFractalFBMScalar(seed, octaves, frequency, lacunarity, gain, + fractalBounding, xs[k], zs[k]); + } + } +} diff --git a/tools/simd-bench/src/simdbench/VectorSimdKernels.java b/tools/simd-bench/src/simdbench/VectorSimdKernels.java new file mode 100644 index 000000000..2c7b41143 --- /dev/null +++ b/tools/simd-bench/src/simdbench/VectorSimdKernels.java @@ -0,0 +1,74 @@ +package simdbench; + +import jdk.incubator.vector.DoubleVector; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorShape; +import jdk.incubator.vector.VectorSpecies; + +public final class VectorSimdKernels implements SimdKernels { + private static final VectorSpecies DOUBLE_SPECIES = DoubleVector.SPECIES_PREFERRED; + private static final VectorSpecies INT_SPECIES = VectorSpecies.of(int.class, VectorShape.forBitSize(DOUBLE_SPECIES.length() * Integer.SIZE)); + + @Override + public String describe() { + return DOUBLE_SPECIES.length() + "x64-bit lanes, " + DOUBLE_SPECIES.vectorShape(); + } + + @Override + public void roundToInt(double[] source, int[] target, int length) { + int lanes = DOUBLE_SPECIES.length(); + int bound = DOUBLE_SPECIES.loopBound(length); + int index = 0; + for (; index < bound; index += lanes) { + DoubleVector shifted = DoubleVector.fromArray(DOUBLE_SPECIES, source, index).add(0.5D); + IntVector truncated = (IntVector) shifted.convertShape(VectorOperators.D2I, INT_SPECIES, 0); + DoubleVector truncatedBack = (DoubleVector) truncated.convertShape(VectorOperators.I2D, DOUBLE_SPECIES, 0); + VectorMask needsDecrement = shifted.lt(truncatedBack).cast(INT_SPECIES); + truncated.sub(1, needsDecrement).intoArray(target, index); + } + + for (; index < length; index++) { + target[index] = (int) Math.round(source[index]); + } + } + + @Override + public double sum(double[] values, int length) { + int lanes = DOUBLE_SPECIES.length(); + int bound = DOUBLE_SPECIES.loopBound(length); + DoubleVector accumulator = DoubleVector.zero(DOUBLE_SPECIES); + int index = 0; + for (; index < bound; index += lanes) { + accumulator = accumulator.add(DoubleVector.fromArray(DOUBLE_SPECIES, values, index)); + } + + double total = accumulator.reduceLanes(VectorOperators.ADD); + for (; index < length; index++) { + total += values[index]; + } + + return total; + } + + @Override + public double max(double[] values, int length) { + int lanes = DOUBLE_SPECIES.length(); + int bound = DOUBLE_SPECIES.loopBound(length); + DoubleVector accumulator = DoubleVector.broadcast(DOUBLE_SPECIES, Double.NEGATIVE_INFINITY); + int index = 0; + for (; index < bound; index += lanes) { + accumulator = accumulator.max(DoubleVector.fromArray(DOUBLE_SPECIES, values, index)); + } + + double best = accumulator.reduceLanes(VectorOperators.MAX); + for (; index < length; index++) { + if (values[index] > best) { + best = values[index]; + } + } + + return best; + } +}