mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-24 07:40:56 +00:00
d
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
package simdbench;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Random;
|
||||
import jdk.incubator.vector.DoubleVector;
|
||||
import jdk.incubator.vector.LongVector;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
public final class Bench {
|
||||
private static final int WARMUP = 50_000;
|
||||
private static final int ROUNDS = 10;
|
||||
private static final int ARRAY_BATCH = 200_000;
|
||||
private static final int NOISE_LENGTH = 256;
|
||||
private static final long SEED = 1337L;
|
||||
private static final double FREQUENCY = 0.01D;
|
||||
private static final double LACUNARITY = 2.0D;
|
||||
private static final double GAIN = 0.5D;
|
||||
private static final int[] ARRAY_LENGTHS = {256, 1024};
|
||||
private static final int[] OCTAVE_SET = {1, 3, 4, 8};
|
||||
|
||||
private static long longSink = 0L;
|
||||
private static double doubleSink = 0.0D;
|
||||
|
||||
private Bench() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String mode = parseMode(args);
|
||||
printHeader(mode);
|
||||
|
||||
SimdKernels scalarArray = new ScalarSimdKernels();
|
||||
SimdKernels vectorArray = new VectorSimdKernels();
|
||||
NoiseKernels2D scalarNoise = new ScalarNoiseKernels2D();
|
||||
NoiseKernels2D vectorNoise = new VectorNoiseKernels2D();
|
||||
|
||||
System.out.println("Array vector impl: " + vectorArray.describe());
|
||||
System.out.println("Noise vector impl: " + vectorNoise.describe());
|
||||
|
||||
if (mode.equals("both")) {
|
||||
runCorrectness(scalarArray, vectorArray, scalarNoise, vectorNoise);
|
||||
}
|
||||
|
||||
runArrayBenchmarks(mode, scalarArray, vectorArray);
|
||||
runNoiseBenchmarks(mode, scalarNoise, vectorNoise);
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Checksum (guards against dead-code elimination, ignore the values):");
|
||||
System.out.println(" longSink=" + longSink);
|
||||
System.out.println(" doubleSink=" + doubleSink);
|
||||
}
|
||||
|
||||
private static String parseMode(String[] args) {
|
||||
String mode = "both";
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i].equals("--mode") && i + 1 < args.length) {
|
||||
mode = args[i + 1].toLowerCase(Locale.ROOT);
|
||||
} else if (args[i].startsWith("--mode=")) {
|
||||
mode = args[i].substring("--mode=".length()).toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
if (!mode.equals("scalar") && !mode.equals("vector") && !mode.equals("both")) {
|
||||
System.out.println("Unknown mode '" + mode + "', falling back to 'both'.");
|
||||
mode = "both";
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
private static void printHeader(String mode) {
|
||||
VectorSpecies<Double> doubleSpecies = DoubleVector.SPECIES_PREFERRED;
|
||||
VectorSpecies<Long> 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<Double> DS = DoubleVector.SPECIES_PREFERRED;
|
||||
private static final VectorSpecies<Long> 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[]> 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<Long> 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<Double> 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<Double> xGreater = x0.compare(VectorOperators.GT, y0);
|
||||
VectorMask<Long> 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> DOUBLE_SPECIES = DoubleVector.SPECIES_PREFERRED;
|
||||
private static final VectorSpecies<Integer> 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<Integer> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user