From c4ad74c9b2329b299f41b34820c7d55668c3c2b6 Mon Sep 17 00:00:00 2001 From: Ian Preston Date: Wed, 31 Jan 2024 19:21:59 +0000 Subject: [PATCH] Use perfect hash for temperature lookup --- .../CalculateAverage_ianopolousfast.java | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/dev/morling/onebrc/CalculateAverage_ianopolousfast.java b/src/main/java/dev/morling/onebrc/CalculateAverage_ianopolousfast.java index 92e2f6ecb8..c7426c15fd 100644 --- a/src/main/java/dev/morling/onebrc/CalculateAverage_ianopolousfast.java +++ b/src/main/java/dev/morling/onebrc/CalculateAverage_ianopolousfast.java @@ -23,6 +23,7 @@ import java.lang.foreign.MemorySegment; import java.nio.ByteOrder; import java.nio.channels.*; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -44,7 +45,7 @@ public class CalculateAverage_ianopolousfast { public static final int MAX_LINE_LENGTH = 107; public static final int MAX_STATIONS = 1 << 14; - private static final OfLong LONG_LAYOUT = JAVA_LONG_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN); + private static final OfLong LONG_LAYOUT_LE = JAVA_LONG_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); private static final VectorSpecies BYTE_SPECIES = ByteVector.SPECIES_PREFERRED.length() >= 16 ? ByteVector.SPECIES_128 : ByteVector.SPECIES_64; @@ -131,18 +132,31 @@ public static short getMinus(long d) { return ((d & 0xff00000000000000L) ^ 0x2d00000000000000L) != 0 ? 0 : (short) -1; } + private static final long PERFECT_HASH_SEED = -1982870890352534081L; + static final short[] temperatureLookup = new short[5003]; + static { + // use perfect hash from hundredwatt + for (int i = -999; i < 1000; i++) { + String s = Integer.toString(i); + String withDot = s.substring(0, s.length() - 1) + (Math.abs(i) < 10 ? "0" : "") + "." + s.charAt(s.length() - 1); + + byte[] bytes = withDot.getBytes(StandardCharsets.UTF_8); + MemorySegment mem = MemorySegment.ofArray(Arrays.copyOfRange(bytes, 0, 8)); + long d = mem.get(LONG_LAYOUT_LE, 0); + long hash = (d * PERFECT_HASH_SEED) & ~(1L << 63); + int index = (int) (hash % temperatureLookup.length); + if (temperatureLookup[index] != 0) + throw new IllegalStateException("Perfect hash is not perfect!"); + temperatureLookup[index] = (short) i; + } + } + public static void processTemperature(long lineSplit, int size, MemorySegment buffer, Stat station) { - long d = buffer.get(LONG_LAYOUT, lineSplit); - // negative is either 0 or -1 - short negative = getMinus(d); - d = d << (negative * -8); - int dotIndex = size - 2 + negative; - d = (d >> 8) | 0x30000000_00000000L; // add a leading 0 digit - d = d >> 8 * (5 - dotIndex); - short temperature = (short) ((byte) d - '0' + - 10 * (((byte) (d >> 16)) - '0') + - 100 * (((byte) (d >> 24)) - '0')); - temperature = (short) ((temperature ^ negative) - negative); // negative treatment inspired by merkitty + long d = buffer.get(LONG_LAYOUT_LE, lineSplit); + d = d & (-1L >>> 8 * (8 - size)); + long hash = (d * PERFECT_HASH_SEED) & ~(1L << 63); + int temperatureIndex = (int) (hash % temperatureLookup.length); + short temperature = temperatureLookup[temperatureIndex]; station.add(temperature); } @@ -204,6 +218,11 @@ public static Stat[] parseStats(long start1, long end2, MemorySegment buffer) { } } + innerLoop(start1, end2, buffer, stations); + return stations; + } + + private static void innerLoop(long start1, long end2, MemorySegment buffer, Stat[] stations) { while (start1 < end2) { int lineSize1 = lineSize(start1, buffer); long start2 = start1 + lineSize1 + 1; @@ -220,7 +239,6 @@ public static Stat[] parseStats(long start1, long end2, MemorySegment buffer) { else start1 += lineSize1 + 1; } - return stations; } public static class Stat {