diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java index c17939cd..2d3d26f1 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/AbstractNumberParser.java @@ -38,6 +38,7 @@ abstract class AbstractNumberParser { * to check for byte values {@literal <} 0 before accessing this array. */ static final byte[] CHAR_TO_HEX_MAP = new byte[256]; + static final short[] CHAR_TO_HEX_MAP2 = new short[256]; static { Arrays.fill(CHAR_TO_HEX_MAP, OTHER_CLASS); @@ -51,6 +52,10 @@ abstract class AbstractNumberParser { CHAR_TO_HEX_MAP[ch] = (byte) (ch - 'a' + 10); } CHAR_TO_HEX_MAP['.'] = DECIMAL_POINT_CLASS; + for (int i = 0; i < 256; i++) { + byte value = CHAR_TO_HEX_MAP[i]; + CHAR_TO_HEX_MAP2[i] = value < 0 ? value : (short) (value << 4); + } } /** @@ -101,10 +106,13 @@ protected static char charAt(CharSequence str, int i, int endIndex) { * @param ch a character * @return the hex value or a value < 0. */ - protected static int lookupHex(byte ch) { + protected static byte lookupHex(byte ch) { return CHAR_TO_HEX_MAP[ch & 0xff]; } + protected static int lookupHex2(byte high, byte low) { + return CHAR_TO_HEX_MAP2[high & 0xff] | CHAR_TO_HEX_MAP[low & 0xff]; + } /** * Looks the character up in the {@link #CHAR_TO_HEX_MAP} returns * a value < 0 if the character is not in the map. @@ -116,7 +124,7 @@ protected static int lookupHex(byte ch) { * @param ch a character * @return the hex value or a value < 0. */ - protected static int lookupHex(char ch) { + protected static byte lookupHex(char ch) { // The branchy code is faster than the branch-less code. // Branch-less code: return CHAR_TO_HEX_MAP[ch & 0xff] | (127 - ch) >> 31; // Branch-less code: return CHAR_TO_HEX_MAP[(ch|((127-ch)>>31))&0xff]; @@ -124,6 +132,9 @@ protected static int lookupHex(char ch) { return ch < 128 ? CHAR_TO_HEX_MAP[ch] : -1; } + protected static int lookupHex2(char high, char low) { + return (high | low) < 128 ? CHAR_TO_HEX_MAP2[high] | CHAR_TO_HEX_MAP[low] : -1; + } /** * Checks the bounds and returns the end index (exclusive) of the data in the array. * diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java index cd7dfb3b..4742f1ae 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromByteArray.java @@ -85,18 +85,15 @@ private BigInteger parseHexDigits(byte[] str, int from, int to, boolean isNegati if ((numDigits & 1) != 0) { byte chLow = str[from++]; - int valueLow = lookupHex(chLow); - bytes[index++] = (byte) valueLow; + byte valueLow = lookupHex(chLow); + bytes[index++] = valueLow; illegalDigits = valueLow < 0; } int prerollLimit = from + ((to - from) & 7); for (; from < prerollLimit; from += 2) { - byte chHigh = str[from]; - byte chLow = str[from + 1]; - int valueHigh = lookupHex(chHigh); - int valueLow = lookupHex(chLow); - bytes[index++] = (byte) (valueHigh << 4 | valueLow); - illegalDigits |= valueHigh < 0 || valueLow < 0; + int value = lookupHex2(str[from], str[from + 1]); + bytes[index++] = (byte) value; + illegalDigits |= value < 0; } for (; from < to; from += 8, index += 4) { long value = FastDoubleSwar.tryToParseEightHexDigits(str, from); diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java index c5cab614..1712ee7c 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharArray.java @@ -81,18 +81,15 @@ private BigInteger parseHexDigits(char[] str, int from, int to, boolean isNegati if ((numDigits & 1) != 0) { char chLow = str[from++]; - int valueLow = lookupHex(chLow); - bytes[index++] = (byte) valueLow; + byte valueLow = lookupHex(chLow); + bytes[index++] = valueLow; illegalDigits = valueLow < 0; } int prerollLimit = from + ((to - from) & 7); for (; from < prerollLimit; from += 2) { - char chHigh = str[from]; - char chLow = str[from + 1]; - int valueHigh = lookupHex(chHigh); - int valueLow = lookupHex(chLow); - bytes[index++] = (byte) (valueHigh << 4 | valueLow); - illegalDigits |= valueHigh < 0 || valueLow < 0; + int value = lookupHex2(str[from], str[from + 1]); + bytes[index++] = (byte) value; + illegalDigits |= value < 0; } for (; from < to; from += 8, index += 4) { long value = FastDoubleSwar.tryToParseEightHexDigits(str, from); diff --git a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java index c03535e8..b36de2a3 100644 --- a/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java +++ b/fastdoubleparser-dev/src/main/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JavaBigIntegerFromCharSequence.java @@ -82,18 +82,15 @@ private BigInteger parseHexDigits(CharSequence str, int from, int to, boolean is boolean illegalDigits = false; if ((numDigits & 1) != 0) { char chLow = str.charAt(from++); - int valueLow = lookupHex(chLow); - bytes[index++] = (byte) valueLow; + byte valueLow = lookupHex(chLow); + bytes[index++] = valueLow; illegalDigits = valueLow < 0; } int prerollLimit = from + ((to - from) & 7); for (; from < prerollLimit; from += 2) { - char chHigh = str.charAt(from); - char chLow = str.charAt(from + 1); - int valueHigh = lookupHex(chHigh); - int valueLow = lookupHex(chLow); - bytes[index++] = (byte) (valueHigh << 4 | valueLow); - illegalDigits |= valueLow < 0 || valueHigh < 0; + int value = lookupHex2(str.charAt(from), str.charAt(from + 1)); + bytes[index++] = (byte) value; + illegalDigits |= value < 0; } for (; from < to; from += 8, index += 4) { long value = FastDoubleSwar.tryToParseEightHexDigits(str, from); diff --git a/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhLookupHex2.java b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhLookupHex2.java new file mode 100644 index 00000000..07ba4b8d --- /dev/null +++ b/fastdoubleparser-dev/src/test/java/ch.randelshofer.fastdoubleparser/ch/randelshofer/fastdoubleparser/JmhLookupHex2.java @@ -0,0 +1,91 @@ +/* + * @(#)JmhFloat.java + * Copyright © 2023 Werner Randelshofer, Switzerland. MIT License. + */ +package ch.randelshofer.fastdoubleparser; + +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.concurrent.TimeUnit; + +/** + * Benchmarks for selected floating point strings. + *
+ * # JMH version: 1.36
+ * # JDK 20.0.1, OpenJDK 64-Bit Server VM, 20.0.1+9-29
+ * # Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
+ *
+ * Benchmark    (str)  Mode  Cnt    Score   Error  Units
+ * lookupHex2Byte      avgt    5  2.020 ± 0.009  ns/op
+ * lookupHex2Char      avgt    5  2.019 ± 0.010  ns/op
+ * lookupHexTwiceByte  avgt    5  2.336 ± 0.057  ns/op
+ * lookupHexTwiceChar  avgt    5  2.321 ± 0.075  ns/op
+ *
+ * Process finished with exit code 0
+ * 
+ */ + +@Fork(value = 1) +@Measurement(iterations = 5, time = 1) +@Warmup(iterations = 2, time = 1) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode(Mode.AverageTime) +@State(Scope.Benchmark) +public class JmhLookupHex2 { + + public char highChar = 'A'; + public char lowChar = '1'; + public char highByte = 'A'; + public char lowByte = '1'; + public boolean illegalDigits; + + @Setup(Level.Iteration) + public void prepare(){ + illegalDigits = false;// otherwise it seems that evaluation of expression for "illegalDigits" is skipped + } + + // several lines of code added to imitate the context, which optimized method is situated in + @Benchmark + public void lookupHex2Char(Blackhole blackhole) { + int result = AbstractNumberParser.lookupHex2(highChar, lowChar); + byte b = (byte) result; + blackhole.consume(b); + illegalDigits |= result < 0; + blackhole.consume(illegalDigits); + } + + @Benchmark + public void lookupHexTwiceChar(Blackhole blackhole) { + int high = AbstractNumberParser.lookupHex(highChar); + int low = AbstractNumberParser.lookupHex(lowChar); + byte b = (byte) (high << 4 | low); + blackhole.consume(b); + illegalDigits |= low < 0 | high < 0; + blackhole.consume(illegalDigits); + } + + @Benchmark + public void lookupHex2Byte(Blackhole blackhole) { + int result = AbstractNumberParser.lookupHex2(highByte, lowByte); + byte b = (byte) result; + blackhole.consume(b); + illegalDigits |= result < 0; + blackhole.consume(illegalDigits); + } + + @Benchmark + public void lookupHexTwiceByte(Blackhole blackhole) { + int high = AbstractNumberParser.lookupHex(highByte); + int low = AbstractNumberParser.lookupHex(lowByte); + byte b = (byte) (high << 4 | low); + blackhole.consume(b); + illegalDigits |= low < 0 | high < 0; + blackhole.consume(illegalDigits); + } +} + + + + +