diff --git a/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/LookupHelper.java b/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/LookupHelper.java
index 8d3efdf6..47d81182 100644
--- a/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/LookupHelper.java
+++ b/pbj-core/pbj-compiler/src/main/java/com/hedera/pbj/compiler/impl/LookupHelper.java
@@ -252,7 +252,7 @@ public String getFullyQualifiedProtoName(final File protoSrcFile, final ParserRu
final Object[] importsArray =
protoFileImports.get(protoSrcFile.getAbsolutePath()).toArray();
final String importsString = Arrays.toString(importsArray);
- throw new PbjCompilerException(FAILED_TO_FIND_MSG_TYPE_MESSAGE.formatted(context, protoSrcFile, importsString));
+ throw new PbjCompilerException(FAILED_TO_FIND_MSG_TYPE_MESSAGE.formatted(context.getText(), protoSrcFile, importsString));
}
/**
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/NonCryptographicHashing.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/NonCryptographicHashing.java
new file mode 100644
index 00000000..455283f3
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/NonCryptographicHashing.java
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime;
+
+import com.hedera.pbj.runtime.io.UnsafeUtils;
+import edu.umd.cs.findbugs.annotations.NonNull;
+import java.nio.ByteBuffer;
+
+/**
+ * This class contains a collection of methods for hashing basic data types.
+ * Hashes are not cryptographically secure, and are intended to be used when
+ * implementing {@link Object#hashCode()} or similar functionality.
+ */
+public final class NonCryptographicHashing {
+ // This class is not meant to be instantiated.
+ private NonCryptographicHashing() {}
+
+ public static int hash32(@NonNull final byte[] bytes) {
+ return hash32(bytes, 0, bytes.length);
+ }
+
+ public static int hash32(@NonNull final byte[] bytes, final int position, final int length) {
+ int hash = 1;
+ int i = position;
+ int end = position + length - 31;
+ // fast loop for large byte arrays
+ for (; i < end; i += 32) {
+ int int1 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i);
+ int int2 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 4);
+ int int3 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 8);
+ int int4 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 12);
+ int int5 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 16);
+ int int6 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 20);
+ int int7 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 24);
+ int int8 = UnsafeUtils.getIntUnsafeLittleEndian(bytes, i + 28);
+ hash = perm32(hash ^ int1);
+ hash = perm32(hash ^ int2);
+ hash = perm32(hash ^ int3);
+ hash = perm32(hash ^ int4);
+ hash = perm32(hash ^ int5);
+ hash = perm32(hash ^ int6);
+ hash = perm32(hash ^ int7);
+ hash = perm32(hash ^ int8);
+ }
+
+ // Accumulate the hash in 32-bit chunks. If the length is not a multiple of 4, then read
+ // as many complete 4 byte chunks as possible.
+ end = position + length - 3;
+ for (; i < end; i += 4) {
+ hash = perm32(hash ^ UnsafeUtils.getIntUnsafeLittleEndian(bytes, i));
+ }
+
+ // Construct a trailing int. If the segment of the byte array we read was exactly a multiple of 4 bytes,
+ // then we will append "0x0000007F" to the end of the hash. If we had 1 byte remaining, then
+ // we will append "0x00007FXX" where XX is the value of the last byte, and so on.
+ int tail = 0x7F;
+ int start = i;
+ i = position + length - 1;
+ for (; i >= start; i--) {
+ tail <<= 8;
+ tail ^= bytes[i];
+ }
+
+ // Combine the tail with the previous hash.
+ hash = perm32(hash ^ tail);
+
+ return hash;
+ }
+
+ public static int hash32old(@NonNull final byte[] bytes, final int position, final int length) {
+ int hash = 1;
+ int i = position;
+ int end = position + length - 3;
+ for (; i < end; i += 4) {
+ hash = perm32(hash ^ UnsafeUtils.getIntUnsafeLittleEndian(bytes, i));
+ }
+
+ // Construct a trailing int. If the segment of the byte array we read was exactly a multiple of 4 bytes,
+ // then we will append "0x0000007F" to the end of the hash. If we had 1 byte remaining, then
+ // we will append "0x00007FXX" where XX is the value of the last byte, and so on.
+ int tail = 0x7F;
+ int start = i;
+ i = position + length - 1;
+ for (; i >= start; i--) {
+ tail <<= 8;
+ tail ^= bytes[i];
+ }
+
+ // Combine the tail with the previous hash.
+ hash = perm32(hash ^ tail);
+
+ return hash;
+ }
+
+ private static int perm32(int x) {
+ // This is necessary so that 0 does not hash to 0. As a side effect, this constant will hash to 0.
+ // It was randomly generated (not using Java), so that it will occur in practice less often than more
+ // common numbers like 0 or -1 or Integer.MAX_VALUE.
+ x ^= 0x5e8a016a;
+
+ // Shifts: {30, 27, 16, 20, 5, 18, 10, 24, 30}
+ x += x << 30;
+ x ^= x >>> 27;
+ x += x << 16;
+ x ^= x >>> 20;
+ x += x << 5;
+ x ^= x >>> 18;
+ x += x << 10;
+ x ^= x >>> 24;
+ x += x << 30;
+ return x;
+ }
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for 1 long.
+ *
+ * @param x0 a single long
+ * @return a non-cryptographic long hash
+ */
+ public static long hash64(final long x0) {
+ return perm64(x0);
+ }
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for a byte array.
+ *
+ * @param bytes
+ * a byte array
+ * @return a non-cryptographic long hash
+ */
+ public static long hash64(@NonNull final byte[] bytes) {
+ return hash64(bytes, 0, bytes.length);
+ }
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for contents of the given byte array within indexes position
+ * (inclusive) and position + length (exclusive).
+ *
+ * @param bytes A byte array. Must not be null. Can be empty.
+ * @param position The starting position within the byte array to begin hashing from. Must be non-negative,
+ * and must be less than the length of the array, and position + length must also be
+ * less than or equal to the length of the array.
+ * @param length
+ * The number of bytes to hash. Must be non-negative, and must be such that position + length
+ * is less than or equal to the length of the byte array.
+ *
+ * @return a non-cryptographic long hash
+ */
+ public static int hash64xor32(@NonNull final byte[] bytes, final int position, final int length) {
+ long hash64 = hash64(bytes, position, length);
+ // Return the upper 32 XOR lower 32 bits of the hash.
+ return (int) ((hash64 >>> 32) ^ (hash64 & 0xFFFFFFFFL));
+ }
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for contents of the given byte array within indexes position
+ * (inclusive) and position + length (exclusive).
+ *
+ * @param bytes A byte array. Must not be null. Can be empty.
+ * @param position The starting position within the byte array to begin hashing from. Must be non-negative,
+ * and must be less than the length of the array, and position + length must also be
+ * less than or equal to the length of the array.
+ * @param length
+ * The number of bytes to hash. Must be non-negative, and must be such that position + length
+ * is less than or equal to the length of the byte array.
+ *
+ * @return a non-cryptographic long hash
+ */
+ public static int hash64upper32(@NonNull final byte[] bytes, final int position, final int length) {
+ long hash64 = hash64(bytes, position, length);
+ // Return the upper 32 bits of the hash.
+ return (int) ((hash64 >>> 32) & 0xFFFFFFFFL);
+ }
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for contents of the given byte array within indexes position
+ * (inclusive) and position + length (exclusive).
+ *
+ * @param bytes A byte array. Must not be null. Can be empty.
+ * @param position The starting position within the byte array to begin hashing from. Must be non-negative,
+ * and must be less than the length of the array, and position + length must also be
+ * less than or equal to the length of the array.
+ * @param length
+ * The number of bytes to hash. Must be non-negative, and must be such that position + length
+ * is less than or equal to the length of the byte array.
+ *
+ * @return a non-cryptographic long hash
+ */
+ public static long hash64(@NonNull final byte[] bytes, final int position, final int length) {
+ // Accumulate the hash in 64-bit chunks. If the length is not a multiple of 8, then read
+ // as many complete 8 byte chunks as possible.
+ long hash = 1;
+ int i = position;
+ int end = position + length - 7;
+ for (; i < end; i += 8) {
+ hash = perm64(hash ^ UnsafeUtils.getLongNoChecksLittleEndian(bytes, i));
+ }
+
+ // Construct a trailing long. If the segment of the byte array we read was exactly a multiple of 8 bytes,
+ // then we will append "0x000000000000007F" to the end of the hash. If we had 1 byte remaining, then
+ // we will append "0x0000000000007FXX" where XX is the value of the last byte, and so on.
+ long tail = 0x7F;
+ int start = i;
+ i = position + length - 1;
+ for (; i >= start; i--) {
+ tail <<= 8;
+ tail ^= bytes[i];
+ }
+
+ // Combine the tail with the previous hash.
+ hash = perm64(hash ^ tail);
+
+ return hash;
+ }
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for a ByteBuffer covering all bytes from position to limit.
+ *
+ * @param buf a byte buffer to compute the hash from
+ * @return a non-cryptographic long hash
+ */
+ public static long hash64(@NonNull final ByteBuffer buf) {
+ long hash = perm64(buf.remaining());
+ final int p = buf.position();
+ final int l = buf.limit();
+ for (int i = p; i < l; i += 8) {
+ final int remaining = l - i;
+ if (remaining < 8) {
+ // If there are less than 8 bytes remaining, we need to pad with zeros.
+ long value = 0;
+ for (int j = 0; j < remaining; j++) {
+ value |= (UnsafeUtils.getHeapBufferByteNoChecks(buf, i + j) & 0xffL) << (8 * (7 - j));
+ }
+ hash = perm64(hash ^ value);
+ break;
+ } else {
+ // If there are 8 or more bytes remaining, we can read a full long.
+ hash = perm64(hash ^ buf.getLong(i));
+ }
+ }
+ return hash;
+ }
+
+ /**
+ *
+ * A permutation (invertible function) on 64 bits. The constants were found by automated search, to
+ * optimize avalanche. Avalanche means that for a random number x, flipping bit i of x has about a
+ * 50 percent chance of flipping bit j of perm64(x). For each possible pair (i,j), this function achieves
+ * a probability between 49.8 and 50.2 percent.
+ *
+ *
+ * Warning: there currently exist production use cases that will break if this hashing algorithm is changed.
+ * If modifications to this hashing algorithm are ever required, they must be raised with the maintainers
+ * of the Hiero Consensus Node and probably the Hiero Technical Steering Committee.
+ */
+ private static long perm64(long x) {
+ // This is necessary so that 0 does not hash to 0. As a side effect, this constant will hash to 0.
+ // It was randomly generated (not using Java), so that it will occur in practice less often than more
+ // common numbers like 0 or -1 or Long.MAX_VALUE.
+ x ^= 0x5e8a016a5eb99c18L;
+
+ // Shifts: {30, 27, 16, 20, 5, 18, 10, 24, 30}
+ x += x << 30;
+ x ^= x >>> 27;
+ x += x << 16;
+ x ^= x >>> 20;
+ x += x << 5;
+ x ^= x >>> 18;
+ x += x << 10;
+ x ^= x >>> 24;
+ x += x << 30;
+ return x;
+ }
+}
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/AbstractHashStream.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/AbstractHashStream.java
new file mode 100644
index 00000000..56ccfdb2
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/AbstractHashStream.java
@@ -0,0 +1,1365 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime.hashing;
+
+import static com.hedera.pbj.runtime.hashing.ByteArrayUtil.getChar;
+import static com.hedera.pbj.runtime.hashing.ByteArrayUtil.getInt;
+import static com.hedera.pbj.runtime.hashing.ByteArrayUtil.getLong;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+import java.util.function.ToLongFunction;
+
+interface AbstractHashStream extends HashStream {
+
+ @Override
+ default HashStream putBoolean(boolean v) {
+ putByte((byte) (v ? 1 : 0));
+ return this;
+ }
+
+ @Override
+ default HashStream putBooleans(boolean[] x) {
+ return putBooleans(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putBooleans(boolean[] x, int off, int len) {
+ int end = len + off;
+ while (off <= end - 8) {
+ long b0 = (x[off] ? 1L : 0L) << (0);
+ long b1 = (x[off + 1] ? 1L : 0L) << (8);
+ long b2 = (x[off + 2] ? 1L : 0L) << (8 * 2);
+ long b3 = (x[off + 3] ? 1L : 0L) << (8 * 3);
+ long b4 = (x[off + 4] ? 1L : 0L) << (8 * 4);
+ long b5 = (x[off + 5] ? 1L : 0L) << (8 * 5);
+ long b6 = (x[off + 6] ? 1L : 0L) << (8 * 6);
+ long b7 = (x[off + 7] ? 1L : 0L) << (8 * 7);
+ putLong(b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7);
+ off += 8;
+ }
+ if (off <= end - 4) {
+ int b0 = (x[off] ? 1 : 0) << (0);
+ int b1 = (x[off + 1] ? 1 : 0) << (8);
+ int b2 = (x[off + 2] ? 1 : 0) << (8 * 2);
+ int b3 = (x[off + 3] ? 1 : 0) << (8 * 3);
+ putInt(b0 | b1 | b2 | b3);
+ off += 4;
+ }
+ if (off <= end - 2) {
+ int b0 = (x[off] ? 1 : 0) << (0);
+ int b1 = (x[off + 1] ? 1 : 0) << (8);
+ putChar((char) (b0 | b1));
+ off += 2;
+ }
+ if (off < end) {
+ putBoolean(x[off]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putBooleanArray(boolean[] x) {
+ return putBooleans(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putBytes(byte[] b) {
+ putBytes(b, 0, b.length);
+ return this;
+ }
+
+ @Override
+ default HashStream putBytes(byte[] b, int off, int len) {
+ int end = len + off;
+ while (off <= end - 8) {
+ putLong(getLong(b, off));
+ off += 8;
+ }
+ if (off <= end - 4) {
+ putInt(getInt(b, off));
+ off += 4;
+ }
+ if (off <= end - 2) {
+ putChar(getChar(b, off));
+ off += 2;
+ }
+ if (off < end) {
+ putByte(b[off]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putByteArray(byte[] x) {
+ return putBytes(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putChar(char v) {
+ putShort((short) v);
+ return this;
+ }
+
+ @Override
+ default HashStream putChars(char[] x) {
+ return putChars(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putChars(char[] x, int off, int len) {
+ int end = len + off;
+ while (off <= end - 4) {
+ long b0 = (long) x[off] << (0);
+ long b1 = (long) x[off + 1] << (16);
+ long b2 = (long) x[off + 2] << (16 * 2);
+ long b3 = (long) x[off + 3] << (16 * 3);
+ putLong(b0 | b1 | b2 | b3);
+ off += 4;
+ }
+ if (off <= end - 2) {
+ int b0 = x[off] << (0);
+ int b1 = x[off + 1] << (16);
+ putInt(b0 | b1);
+ off += 2;
+ }
+ if (off < end) {
+ putChar(x[off]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putChars(CharSequence s) {
+ int end = s.length();
+ int off = 0;
+ while (off <= end - 4) {
+ putLong(getLong(s, off));
+ off += 4;
+ }
+ if (off <= end - 2) {
+ putInt(getInt(s, off));
+ off += 2;
+ }
+ if (off < end) {
+ putChar(s.charAt(off));
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putCharArray(char[] x) {
+ return putChars(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putString(String s) {
+ putChars(s);
+ putInt(s.length());
+ return this;
+ }
+
+ @Override
+ default HashStream putShort(short v) {
+ putByte((byte) v);
+ putByte((byte) (v >>> 8));
+ return this;
+ }
+
+ @Override
+ default HashStream putShortArray(short[] x) {
+ return putShorts(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putShorts(short[] x) {
+ return putShorts(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putShorts(short[] x, int off, int len) {
+ int end = off + len;
+ while (off <= end - 4) {
+ long b0 = (x[off] & 0xFFFFL) << (0);
+ long b1 = (x[off + 1] & 0xFFFFL) << (16);
+ long b2 = (x[off + 2] & 0xFFFFL) << (16 * 2);
+ long b3 = (x[off + 3] & 0xFFFFL) << (16 * 3);
+ putLong(b0 | b1 | b2 | b3);
+ off += 4;
+ }
+ if (off <= end - 2) {
+ int b0 = (x[off] & 0xFFFF) << (0);
+ int b1 = (x[off + 1] & 0xFFFF) << (16);
+ putInt(b0 | b1);
+ off += 2;
+ }
+ if (off < end) {
+ putShort(x[off]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putInt(int v) {
+ putByte((byte) v);
+ putByte((byte) (v >>> 8));
+ putByte((byte) (v >>> 16));
+ putByte((byte) (v >>> 24));
+ return this;
+ }
+
+ @Override
+ default HashStream putIntArray(int[] x) {
+ return putInts(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putInts(int[] x) {
+ return putInts(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putInts(int[] x, int off, int len) {
+ int end = off + len;
+ while (off <= end - 2) {
+ long b0 = x[off] & 0xFFFFFFFFL;
+ long b1 = (long) x[off + 1] << 32;
+ putLong(b0 | b1);
+ off += 2;
+ }
+ if (off < end) {
+ putInt(x[off]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putLong(long v) {
+ putInt((int) v);
+ putInt((int) (v >> 32));
+ return this;
+ }
+
+ @Override
+ default HashStream putLongArray(long[] x) {
+ return putLongs(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putLongs(long[] x) {
+ return putLongs(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putLongs(long[] x, int off, int len) {
+ for (int i = 0; i < len; ++i) {
+ putLong(x[off + i]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putFloat(float v) {
+ putInt(Float.floatToRawIntBits(v));
+ return this;
+ }
+
+ @Override
+ default HashStream putFloats(float[] x) {
+ return putFloats(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putFloats(float[] x, int off, int len) {
+ int end = off + len;
+ while (off <= end - 2) {
+ long b0 = Float.floatToRawIntBits(x[off]) & 0xFFFFFFFFL;
+ long b1 = (long) Float.floatToRawIntBits(x[off + 1]) << 32;
+ putLong(b0 | b1);
+ off += 2;
+ }
+ if (off < end) {
+ putFloat(x[off]);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putFloatArray(float[] x) {
+ return putFloats(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putDouble(double v) {
+ putLong(Double.doubleToRawLongBits(v));
+ return this;
+ }
+
+ @Override
+ default HashStream putDoubleArray(double[] x) {
+ return putDoubles(x).putInt(x.length);
+ }
+
+ @Override
+ default HashStream putDoubles(double[] x) {
+ return putDoubles(x, 0, x.length);
+ }
+
+ @Override
+ default HashStream putDoubles(double[] x, int off, int len) {
+ for (int i = 0; i < len; ++i) {
+ putDouble(x[off + i]);
+ }
+ return this;
+ }
+
+ private void putSorted(long l0, long l1) {
+ if (l1 <= l0) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ }
+
+ private void putSorted(long l0, long l1, long l2) {
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3) {
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3, long l4) {
+ // generated from http://pages.ripco.net/~jgamble/nw.html
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ if (l2 > l4) {
+ long t = l2;
+ l2 = l4;
+ l4 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ if (l1 > l4) {
+ long t = l1;
+ l1 = l4;
+ l4 = t;
+ }
+ if (l0 > l3) {
+ long t = l0;
+ l0 = l3;
+ l3 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ putLong(l4);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3, long l4, long l5) {
+ // generated from http://pages.ripco.net/~jgamble/nw.html
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ if (l4 > l5) {
+ long t = l4;
+ l4 = l5;
+ l5 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l3 > l5) {
+ long t = l3;
+ l3 = l5;
+ l5 = t;
+ }
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ if (l2 > l5) {
+ long t = l2;
+ l2 = l5;
+ l5 = t;
+ }
+ if (l0 > l3) {
+ long t = l0;
+ l0 = l3;
+ l3 = t;
+ }
+ if (l1 > l4) {
+ long t = l1;
+ l1 = l4;
+ l4 = t;
+ }
+ if (l2 > l4) {
+ long t = l2;
+ l2 = l4;
+ l4 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ putLong(l4);
+ putLong(l5);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3, long l4, long l5, long l6) {
+ // generated from http://pages.ripco.net/~jgamble/nw.html
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ if (l5 > l6) {
+ long t = l5;
+ l5 = l6;
+ l6 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l3 > l5) {
+ long t = l3;
+ l3 = l5;
+ l5 = t;
+ }
+ if (l4 > l6) {
+ long t = l4;
+ l4 = l6;
+ l6 = t;
+ }
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l4 > l5) {
+ long t = l4;
+ l4 = l5;
+ l5 = t;
+ }
+ if (l2 > l6) {
+ long t = l2;
+ l2 = l6;
+ l6 = t;
+ }
+ if (l0 > l4) {
+ long t = l0;
+ l0 = l4;
+ l4 = t;
+ }
+ if (l1 > l5) {
+ long t = l1;
+ l1 = l5;
+ l5 = t;
+ }
+ if (l0 > l3) {
+ long t = l0;
+ l0 = l3;
+ l3 = t;
+ }
+ if (l2 > l5) {
+ long t = l2;
+ l2 = l5;
+ l5 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l2 > l4) {
+ long t = l2;
+ l2 = l4;
+ l4 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ putLong(l4);
+ putLong(l5);
+ putLong(l6);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3, long l4, long l5, long l6, long l7) {
+ // generated from http://pages.ripco.net/~jgamble/nw.html
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ if (l4 > l5) {
+ long t = l4;
+ l4 = l5;
+ l5 = t;
+ }
+ if (l6 > l7) {
+ long t = l6;
+ l6 = l7;
+ l7 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l4 > l6) {
+ long t = l4;
+ l4 = l6;
+ l6 = t;
+ }
+ if (l5 > l7) {
+ long t = l5;
+ l5 = l7;
+ l7 = t;
+ }
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ if (l5 > l6) {
+ long t = l5;
+ l5 = l6;
+ l6 = t;
+ }
+ if (l0 > l4) {
+ long t = l0;
+ l0 = l4;
+ l4 = t;
+ }
+ if (l3 > l7) {
+ long t = l3;
+ l3 = l7;
+ l7 = t;
+ }
+ if (l1 > l5) {
+ long t = l1;
+ l1 = l5;
+ l5 = t;
+ }
+ if (l2 > l6) {
+ long t = l2;
+ l2 = l6;
+ l6 = t;
+ }
+ if (l1 > l4) {
+ long t = l1;
+ l1 = l4;
+ l4 = t;
+ }
+ if (l3 > l6) {
+ long t = l3;
+ l3 = l6;
+ l6 = t;
+ }
+ if (l2 > l4) {
+ long t = l2;
+ l2 = l4;
+ l4 = t;
+ }
+ if (l3 > l5) {
+ long t = l3;
+ l3 = l5;
+ l5 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ putLong(l4);
+ putLong(l5);
+ putLong(l6);
+ putLong(l7);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3, long l4, long l5, long l6, long l7, long l8) {
+ // generated from http://pages.ripco.net/~jgamble/nw.html
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ if (l6 > l7) {
+ long t = l6;
+ l6 = l7;
+ l7 = t;
+ }
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ if (l4 > l5) {
+ long t = l4;
+ l4 = l5;
+ l5 = t;
+ }
+ if (l7 > l8) {
+ long t = l7;
+ l7 = l8;
+ l8 = t;
+ }
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ if (l6 > l7) {
+ long t = l6;
+ l6 = l7;
+ l7 = t;
+ }
+ if (l2 > l5) {
+ long t = l2;
+ l2 = l5;
+ l5 = t;
+ }
+ if (l0 > l3) {
+ long t = l0;
+ l0 = l3;
+ l3 = t;
+ }
+ if (l1 > l4) {
+ long t = l1;
+ l1 = l4;
+ l4 = t;
+ }
+ if (l5 > l8) {
+ long t = l5;
+ l5 = l8;
+ l8 = t;
+ }
+ if (l3 > l6) {
+ long t = l3;
+ l3 = l6;
+ l6 = t;
+ }
+ if (l4 > l7) {
+ long t = l4;
+ l4 = l7;
+ l7 = t;
+ }
+ if (l2 > l5) {
+ long t = l2;
+ l2 = l5;
+ l5 = t;
+ }
+ if (l0 > l3) {
+ long t = l0;
+ l0 = l3;
+ l3 = t;
+ }
+ if (l1 > l4) {
+ long t = l1;
+ l1 = l4;
+ l4 = t;
+ }
+ if (l5 > l7) {
+ long t = l5;
+ l5 = l7;
+ l7 = t;
+ }
+ if (l2 > l6) {
+ long t = l2;
+ l2 = l6;
+ l6 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l4 > l6) {
+ long t = l4;
+ l4 = l6;
+ l6 = t;
+ }
+ if (l2 > l4) {
+ long t = l2;
+ l2 = l4;
+ l4 = t;
+ }
+ if (l5 > l6) {
+ long t = l5;
+ l5 = l6;
+ l6 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ putLong(l4);
+ putLong(l5);
+ putLong(l6);
+ putLong(l7);
+ putLong(l8);
+ }
+
+ private void putSorted(long l0, long l1, long l2, long l3, long l4, long l5, long l6, long l7, long l8, long l9) {
+ // generated from http://pages.ripco.net/~jgamble/nw.html
+ if (l4 > l9) {
+ long t = l4;
+ l4 = l9;
+ l9 = t;
+ }
+ if (l3 > l8) {
+ long t = l3;
+ l3 = l8;
+ l8 = t;
+ }
+ if (l2 > l7) {
+ long t = l2;
+ l2 = l7;
+ l7 = t;
+ }
+ if (l1 > l6) {
+ long t = l1;
+ l1 = l6;
+ l6 = t;
+ }
+ if (l0 > l5) {
+ long t = l0;
+ l0 = l5;
+ l5 = t;
+ }
+ if (l1 > l4) {
+ long t = l1;
+ l1 = l4;
+ l4 = t;
+ }
+ if (l6 > l9) {
+ long t = l6;
+ l6 = l9;
+ l9 = t;
+ }
+ if (l0 > l3) {
+ long t = l0;
+ l0 = l3;
+ l3 = t;
+ }
+ if (l5 > l8) {
+ long t = l5;
+ l5 = l8;
+ l8 = t;
+ }
+ if (l0 > l2) {
+ long t = l0;
+ l0 = l2;
+ l2 = t;
+ }
+ if (l3 > l6) {
+ long t = l3;
+ l3 = l6;
+ l6 = t;
+ }
+ if (l7 > l9) {
+ long t = l7;
+ l7 = l9;
+ l9 = t;
+ }
+ if (l0 > l1) {
+ long t = l0;
+ l0 = l1;
+ l1 = t;
+ }
+ if (l2 > l4) {
+ long t = l2;
+ l2 = l4;
+ l4 = t;
+ }
+ if (l5 > l7) {
+ long t = l5;
+ l5 = l7;
+ l7 = t;
+ }
+ if (l8 > l9) {
+ long t = l8;
+ l8 = l9;
+ l9 = t;
+ }
+ if (l1 > l2) {
+ long t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+ if (l4 > l6) {
+ long t = l4;
+ l4 = l6;
+ l6 = t;
+ }
+ if (l7 > l8) {
+ long t = l7;
+ l7 = l8;
+ l8 = t;
+ }
+ if (l3 > l5) {
+ long t = l3;
+ l3 = l5;
+ l5 = t;
+ }
+ if (l2 > l5) {
+ long t = l2;
+ l2 = l5;
+ l5 = t;
+ }
+ if (l6 > l8) {
+ long t = l6;
+ l6 = l8;
+ l8 = t;
+ }
+ if (l1 > l3) {
+ long t = l1;
+ l1 = l3;
+ l3 = t;
+ }
+ if (l4 > l7) {
+ long t = l4;
+ l4 = l7;
+ l7 = t;
+ }
+ if (l2 > l3) {
+ long t = l2;
+ l2 = l3;
+ l3 = t;
+ }
+ if (l6 > l7) {
+ long t = l6;
+ l6 = l7;
+ l7 = t;
+ }
+ if (l3 > l4) {
+ long t = l3;
+ l3 = l4;
+ l4 = t;
+ }
+ if (l5 > l6) {
+ long t = l5;
+ l5 = l6;
+ l6 = t;
+ }
+ if (l4 > l5) {
+ long t = l4;
+ l4 = l5;
+ l5 = t;
+ }
+ putLong(l0);
+ putLong(l1);
+ putLong(l2);
+ putLong(l3);
+ putLong(l4);
+ putLong(l5);
+ putLong(l6);
+ putLong(l7);
+ putLong(l8);
+ putLong(l9);
+ }
+
+ private void putUnorderedRandomAccessList(final List data, final ToLongFunction super T> elementHasher) {
+
+ int size = data.size();
+
+ // for data sizes up to 10 there are fast implementations to avoid the allocation of an array
+ // used for sorting
+ switch (size) {
+ case 0:
+ break;
+ case 1:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.getFirst());
+ putLong(elementHash0);
+ }
+ break;
+ case 2:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ putSorted(elementHash0, elementHash1);
+ }
+ break;
+ case 3:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ putSorted(elementHash0, elementHash1, elementHash2);
+ }
+ break;
+ case 4:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ putSorted(elementHash0, elementHash1, elementHash2, elementHash3);
+ }
+ break;
+ case 5:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ long elementHash4 = elementHasher.applyAsLong(data.get(4));
+ putSorted(elementHash0, elementHash1, elementHash2, elementHash3, elementHash4);
+ }
+ break;
+ case 6:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ long elementHash4 = elementHasher.applyAsLong(data.get(4));
+ long elementHash5 = elementHasher.applyAsLong(data.get(5));
+ putSorted(elementHash0, elementHash1, elementHash2, elementHash3, elementHash4, elementHash5);
+ }
+ break;
+ case 7:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ long elementHash4 = elementHasher.applyAsLong(data.get(4));
+ long elementHash5 = elementHasher.applyAsLong(data.get(5));
+ long elementHash6 = elementHasher.applyAsLong(data.get(6));
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6);
+ }
+ break;
+ case 8:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ long elementHash4 = elementHasher.applyAsLong(data.get(4));
+ long elementHash5 = elementHasher.applyAsLong(data.get(5));
+ long elementHash6 = elementHasher.applyAsLong(data.get(6));
+ long elementHash7 = elementHasher.applyAsLong(data.get(7));
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6,
+ elementHash7);
+ }
+ break;
+ case 9:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ long elementHash4 = elementHasher.applyAsLong(data.get(4));
+ long elementHash5 = elementHasher.applyAsLong(data.get(5));
+ long elementHash6 = elementHasher.applyAsLong(data.get(6));
+ long elementHash7 = elementHasher.applyAsLong(data.get(7));
+ long elementHash8 = elementHasher.applyAsLong(data.get(8));
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6,
+ elementHash7,
+ elementHash8);
+ }
+ break;
+ case 10:
+ {
+ long elementHash0 = elementHasher.applyAsLong(data.get(0));
+ long elementHash1 = elementHasher.applyAsLong(data.get(1));
+ long elementHash2 = elementHasher.applyAsLong(data.get(2));
+ long elementHash3 = elementHasher.applyAsLong(data.get(3));
+ long elementHash4 = elementHasher.applyAsLong(data.get(4));
+ long elementHash5 = elementHasher.applyAsLong(data.get(5));
+ long elementHash6 = elementHasher.applyAsLong(data.get(6));
+ long elementHash7 = elementHasher.applyAsLong(data.get(7));
+ long elementHash8 = elementHasher.applyAsLong(data.get(8));
+ long elementHash9 = elementHasher.applyAsLong(data.get(9));
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6,
+ elementHash7,
+ elementHash8,
+ elementHash9);
+ }
+ break;
+ default: {
+ long[] elementHashes = new long[size];
+ for (int i = 0; i < size; ++i) {
+ elementHashes[i] = elementHasher.applyAsLong(data.get(i));
+ }
+ Arrays.sort(elementHashes, 0, size);
+ putLongs(elementHashes, 0, size);
+ }
+ }
+ putInt(size);
+ }
+
+ private void putUnorderedCollection(final Collection data, final ToLongFunction super T> elementHasher) {
+
+ int size = data.size();
+
+ // for data sizes up to 10 there are fast implementations to avoid the allocation of an array
+ // used for sorting
+ switch (size) {
+ case 0:
+ break;
+ case 1:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ putLong(elementHash0);
+ }
+ break;
+ case 2:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ putSorted(elementHash0, elementHash1);
+ }
+ break;
+ case 3:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ putSorted(elementHash0, elementHash1, elementHash2);
+ }
+ break;
+ case 4:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ putSorted(elementHash0, elementHash1, elementHash2, elementHash3);
+ }
+ break;
+ case 5:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ long elementHash4 = elementHasher.applyAsLong(it.next());
+ putSorted(elementHash0, elementHash1, elementHash2, elementHash3, elementHash4);
+ }
+ break;
+ case 6:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ long elementHash4 = elementHasher.applyAsLong(it.next());
+ long elementHash5 = elementHasher.applyAsLong(it.next());
+ putSorted(elementHash0, elementHash1, elementHash2, elementHash3, elementHash4, elementHash5);
+ }
+ break;
+ case 7:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ long elementHash4 = elementHasher.applyAsLong(it.next());
+ long elementHash5 = elementHasher.applyAsLong(it.next());
+ long elementHash6 = elementHasher.applyAsLong(it.next());
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6);
+ }
+ break;
+ case 8:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ long elementHash4 = elementHasher.applyAsLong(it.next());
+ long elementHash5 = elementHasher.applyAsLong(it.next());
+ long elementHash6 = elementHasher.applyAsLong(it.next());
+ long elementHash7 = elementHasher.applyAsLong(it.next());
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6,
+ elementHash7);
+ }
+ break;
+ case 9:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ long elementHash4 = elementHasher.applyAsLong(it.next());
+ long elementHash5 = elementHasher.applyAsLong(it.next());
+ long elementHash6 = elementHasher.applyAsLong(it.next());
+ long elementHash7 = elementHasher.applyAsLong(it.next());
+ long elementHash8 = elementHasher.applyAsLong(it.next());
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6,
+ elementHash7,
+ elementHash8);
+ }
+ break;
+ case 10:
+ {
+ Iterator it = data.iterator();
+ long elementHash0 = elementHasher.applyAsLong(it.next());
+ long elementHash1 = elementHasher.applyAsLong(it.next());
+ long elementHash2 = elementHasher.applyAsLong(it.next());
+ long elementHash3 = elementHasher.applyAsLong(it.next());
+ long elementHash4 = elementHasher.applyAsLong(it.next());
+ long elementHash5 = elementHasher.applyAsLong(it.next());
+ long elementHash6 = elementHasher.applyAsLong(it.next());
+ long elementHash7 = elementHasher.applyAsLong(it.next());
+ long elementHash8 = elementHasher.applyAsLong(it.next());
+ long elementHash9 = elementHasher.applyAsLong(it.next());
+ putSorted(
+ elementHash0,
+ elementHash1,
+ elementHash2,
+ elementHash3,
+ elementHash4,
+ elementHash5,
+ elementHash6,
+ elementHash7,
+ elementHash8,
+ elementHash9);
+ }
+ break;
+ default: {
+ Iterator it = data.iterator();
+ long[] elementHashes = new long[size];
+ for (int i = 0; i < size; ++i) {
+ elementHashes[i] = elementHasher.applyAsLong(it.next());
+ }
+ Arrays.sort(elementHashes, 0, size);
+ putLongs(elementHashes, 0, size);
+ }
+ }
+ putInt(size);
+ }
+
+ @Override
+ default HashStream putOptionalInt(OptionalInt v) {
+ if (v.isPresent()) {
+ putInt(v.getAsInt());
+ putBoolean(true);
+ } else {
+ putBoolean(false);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putOptionalLong(OptionalLong v) {
+ if (v.isPresent()) {
+ putLong(v.getAsLong());
+ putBoolean(true);
+ } else {
+ putBoolean(false);
+ }
+ return this;
+ }
+
+ @Override
+ default HashStream putOptionalDouble(OptionalDouble v) {
+ if (v.isPresent()) {
+ putDouble(v.getAsDouble());
+ putBoolean(true);
+ } else {
+ putBoolean(false);
+ }
+ return this;
+ }
+}
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/ByteArrayUtil.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/ByteArrayUtil.java
new file mode 100644
index 00000000..c339385b
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/ByteArrayUtil.java
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime.hashing;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/** Utility class for byte arrays. */
+ final class ByteArrayUtil {
+
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle INT_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle SHORT_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle CHAR_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(char[].class, ByteOrder.LITTLE_ENDIAN);
+
+ private ByteArrayUtil() {}
+
+ /**
+ * Reads a {@code char} from a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @return the read character
+ */
+ public static char getChar(byte[] b, int off) {
+ return (char) CHAR_HANDLE.get(b, off);
+ }
+
+ /**
+ * Reads an {@code int} value from a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @return the read value
+ */
+ public static int getInt(byte[] b, int off) {
+ return (int) INT_HANDLE.get(b, off);
+ }
+
+ /**
+ * Reads a {@code long} value from a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @return the read value
+ */
+ public static long getLong(byte[] b, int off) {
+ return (long) LONG_HANDLE.get(b, off);
+ }
+
+ /**
+ * Writes a {@code long} value to a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @param v a value
+ */
+ public static void setLong(byte[] b, int off, long v) {
+ LONG_HANDLE.set(b, off, v);
+ }
+
+ /**
+ * Writes an {@code int} value to a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @param v a value
+ */
+ public static void setInt(byte[] b, int off, int v) {
+ INT_HANDLE.set(b, off, v);
+ }
+
+ /**
+ * Writes a {@code short} value to a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @param v a value
+ */
+ public static void setShort(byte[] b, int off, short v) {
+ SHORT_HANDLE.set(b, off, v);
+ }
+
+ /**
+ * Reads a {@code long} value from a {@link CharSequence} with given offset.
+ *
+ * @param charSequence a char sequence
+ * @param off an offset
+ * @return the value
+ */
+ public static long getLong(CharSequence charSequence, int off) {
+ return (long) charSequence.charAt(off)
+ | ((long) charSequence.charAt(off + 1) << 16)
+ | ((long) charSequence.charAt(off + 2) << 32)
+ | ((long) charSequence.charAt(off + 3) << 48);
+ }
+
+ /**
+ * Reads an {@code int} value from a {@link CharSequence} with given offset.
+ *
+ * @param charSequence a char sequence
+ * @param off an offset
+ * @return the value
+ */
+ public static int getInt(CharSequence charSequence, int off) {
+ return (int) charSequence.charAt(off) | ((int) charSequence.charAt(off + 1) << 16);
+ }
+
+ /**
+ * Writes a {@code char} to a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @param v a character
+ */
+ public static void setChar(byte[] b, int off, char v) {
+ CHAR_HANDLE.set(b, off, v);
+ }
+
+ /**
+ * Copies a given number of characters from a {@link CharSequence} into a byte array.
+ *
+ * @param charSequence a char sequence
+ * @param offetCharSequence an offset for the char sequence
+ * @param byteArray a byte array
+ * @param offsetByteArray an offset for the byte array
+ * @param numChars the number of characters to copy
+ */
+ public static void copyCharsToByteArray(
+ CharSequence charSequence, int offetCharSequence, byte[] byteArray, int offsetByteArray, int numChars) {
+ for (int charIdx = 0; charIdx <= numChars - 4; charIdx += 4) {
+ setLong(byteArray, offsetByteArray + (charIdx << 1), getLong(charSequence, offetCharSequence + charIdx));
+ }
+ if ((numChars & 2) != 0) {
+ int charIdx = numChars & 0xFFFFFFFC;
+ setInt(byteArray, offsetByteArray + (charIdx << 1), getInt(charSequence, offetCharSequence + charIdx));
+ }
+ if ((numChars & 1) != 0) {
+ int charIdx = numChars & 0xFFFFFFFE;
+ setChar(byteArray, offsetByteArray + (charIdx << 1), charSequence.charAt(offetCharSequence + charIdx));
+ }
+ }
+}
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/HashSink.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/HashSink.java
new file mode 100644
index 00000000..980143a0
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/HashSink.java
@@ -0,0 +1,571 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime.hashing;
+
+import java.util.*;
+
+/** A sink that accepts various data types contributing to the hash computation. */
+public interface HashSink {
+
+ /**
+ * Adds a byte value to the hash computation.
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putByte(byte v);
+
+ /**
+ * Adds all elements of a {@code byte} array to the hash computation.
+ *
+ *
Unlike {@link #putByteArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putBytes(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putBytes(byte[] x);
+
+ /**
+ * Adds len elements of the given {@code byte} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putByte(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putBytes(byte[] x, int off, int len);
+
+ /**
+ * Adds a {@code byte} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putBytes(x).putInt(x.length);}
+ *
+ * @param x the boolean array
+ * @return this
+ */
+ HashSink putByteArray(byte[] x);
+
+ /**
+ * Adds a boolean value to the hash computation.
+ *
+ *
Equivalent to
+ * {@code putByte(v ? 1 : 0);}
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putBoolean(boolean v);
+
+ /**
+ * Adds all elements of a {@code boolean} array to the hash computation.
+ *
+ *
Unlike {@link #putBooleanArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putBooleans(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putBooleans(boolean[] x);
+
+ /**
+ * Adds len elements of the given {@code boolean} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putBoolean(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putBooleans(boolean[] x, int off, int len);
+
+ /**
+ * Adds a {@code boolean} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putBooleans(x).putInt(x.length);}
+ *
+ * @param x the boolean array
+ * @return this
+ */
+ HashSink putBooleanArray(boolean[] x);
+
+ /**
+ * Adds a short value to the hash computation using little-endian byte order.
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putShort(short v);
+
+ /**
+ * Adds all elements of a {@code short} array to the hash computation.
+ *
+ *
Unlike {@link #putShortArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putShorts(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putShorts(short[] x);
+
+ /**
+ * Adds len elements of the given {@code short} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putShort(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putShorts(short[] x, int off, int len);
+
+ /**
+ * Adds a {@code short} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
{@code hashSink.putShortArray(new short[]{1, 2}).putShortArray}{@code (new short[]{3})}
+ *
+ *
and
+ *
+ *
{@code hashSink.putShortArray}{@code (new short[]{1}).putShortArray}{@code (new short[]{2,
+ * 3})}
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putShorts(x).putInt(x.length);}
+ *
+ * @param x the short array
+ * @return this
+ */
+ HashSink putShortArray(short[] x);
+
+ /**
+ * Adds a char value to the hash computation using little-endian byte order.
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putChar(char v);
+
+ /**
+ * Adds all elements of a {@code char} array to the hash computation.
+ *
+ *
Unlike {@link #putCharArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putChars(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putChars(char[] x);
+
+ /**
+ * Adds len elements of the given {@code char} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putChar(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putChars(char[] x, int off, int len);
+
+ /**
+ * Adds chars to the hash computation.
+ *
+ *
This method does not include the length information. In this way,
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putChars(s).putInt(s.length());}
+ *
+ * @param s the string
+ * @return this
+ */
+ HashSink putString(String s);
+
+ /**
+ * Adds an int value to the hash computation using little-endian byte order.
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putInt(int v);
+
+ /**
+ * Adds all elements of an {@code int} array to the hash computation.
+ *
+ *
Unlike {@link #putIntArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putInts(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putInts(int[] x);
+
+ /**
+ * Adds len elements of the given {@code int} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putInt(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putInts(int[] x, int off, int len);
+
+ /**
+ * Adds an {@code int} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
{@code hashSink.putIntArray}{@code (new int[]{1, 2}).putIntArray}{@code (new int[]{3})}
+ *
+ *
and
+ *
+ *
{@code hashSink.putIntArray}{@code (new int[]{1}).putIntArray}{@code (new int[]{2, 3})}
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code }{@code putInts(x).putInt(x.length);}
+ *
+ * @param x the int array
+ * @return this
+ */
+ HashSink putIntArray(int[] x);
+
+ /**
+ * Adds along long value to the hash computation using little-endian byte order.
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putLong(long v);
+
+ /**
+ * Adds all elements of a {@code long} array to the hash computation.
+ *
+ *
Unlike {@link #putLongArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putLongs(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putLongs(long[] x);
+
+ /**
+ * Adds len elements of the given {@code long} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putLong(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putLongs(long[] x, int off, int len);
+
+ /**
+ * Adds a {@code long} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
{@code hashSink.putLongArray}{@code (new long[]{1, 2}).putLongArray}{@code (new long[]{3})}
+ *
+ *
and
+ *
+ *
{@code hashSink.putLongArray}{@code (new long[]{1}).putLongArray}{@code (new long[]{2, 3})}
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putLongs(x).putInt(x.length);}
+ *
+ * @param x the int array
+ * @return this
+ */
+ HashSink putLongArray(long[] x);
+
+ /**
+ * Adds a float value to the hash computation using little-endian byte order.
+ *
+ *
Equivalent to
+ * {@code putInt(Float.floatToRawIntBits(v));}
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putFloat(float v);
+
+ /**
+ * Adds all elements of a {@code float} array to the hash computation.
+ *
+ *
Unlike {@link #putFloatArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putFloats(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putFloats(float[] x);
+
+ /**
+ * Adds len elements of the given {@code float} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putFloat(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putFloats(float[] x, int off, int len);
+
+ /**
+ * Adds a {@code float} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putFloats(x).putInt(x.length);}
+ *
+ * @param x the float array
+ * @return this
+ */
+ HashSink putFloatArray(float[] x);
+
+ /**
+ * Adds a double value to the hash computation using little-endian byte order.
+ *
+ *
Equivalent to
+ * {@code putLong(Double.doubleToRawLongBits(v));}
+ *
+ * @param v the value
+ * @return this
+ */
+ HashSink putDouble(double v);
+
+ /**
+ * Adds all elements of a {@code double} array to the hash computation.
+ *
+ *
Unlike {@link #putDoubleArray} this method does not add the length of the array.
+ *
+ *
If the array has variable length, and it is just one of many variable-length fields of the
+ * object for which a hash value is calculated, it is highly recommended to also incorporate the
+ * length of the array to improve the hash quality and decrease the chance of hash collisions.
+ *
+ *
Equivalent to
+ * {@code putDoubles(x, 0, x.length);}
+ *
+ * @param x the array
+ * @return this
+ */
+ HashSink putDoubles(double[] x);
+
+ /**
+ * Adds len elements of the given {@code double} array to the hash computation.
+ *
+ *
Equivalent to
+ * {@code for (int i = 0; i < len; i++) putDouble(x[off + i]);}
+ *
+ * @param x the array
+ * @param off the start offset in the array
+ * @param len the number of elements
+ * @return this
+ */
+ HashSink putDoubles(double[] x, int off, int len);
+
+ /**
+ * Adds a {@code double} array to the hash computation.
+ *
+ *
This method includes the length information. In this way,
+ *
+ *
will be different contributions to the hash value computation.
+ *
+ *
Equivalent to
+ * {@code putDoubles(x).putInt(x.length);}
+ *
+ * @param x the double array
+ * @return this
+ */
+ HashSink putDoubleArray(double[] x);
+ //
+ // /**
+ // * Adds an unordered {@link Iterable} (e.g. {@link Set}) to the hash computation.
+ // *
+ // * @param data the iterable
+ // * @param elementHashFunction 64-bit hash function used for individual elements
+ // * @param the element type
+ // * @return this
+ // * @throws OutOfMemoryError if the allocation of a long array, that is able to keep a 64-bit hash
+ // * for each element in the Iterable, fails
+ // */
+ // HashSink putUnorderedIterable(
+ // Iterable data, ToLongFunction super T> elementHashFunction);
+
+ /**
+ * Adds an {@link OptionalInt} to the hash computation.
+ *
+ * @param v the optional value
+ * @return this
+ */
+ HashSink putOptionalInt(OptionalInt v);
+
+ /**
+ * Adds an {@link OptionalLong} to the hash computation.
+ *
+ * @param v the optional value
+ * @return this
+ */
+ HashSink putOptionalLong(OptionalLong v);
+
+ /**
+ * Adds an {@link OptionalDouble} to the hash computation.
+ *
+ * @param v the optional value
+ * @return this
+ */
+ HashSink putOptionalDouble(OptionalDouble v);
+}
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/HashStream.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/HashStream.java
new file mode 100644
index 00000000..7b929b07
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/HashStream.java
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime.hashing;
+
+import java.util.*;
+
+interface HashStream extends HashSink {
+
+ @Override
+ HashStream putByte(byte v);
+
+ @Override
+ HashStream putBytes(byte[] x);
+
+ @Override
+ HashStream putBytes(byte[] x, int off, int len);
+
+ @Override
+ HashStream putByteArray(byte[] x);
+
+ @Override
+ HashStream putBoolean(boolean v);
+
+ @Override
+ HashStream putBooleans(boolean[] x);
+
+ @Override
+ HashStream putBooleans(boolean[] x, int off, int len);
+
+ @Override
+ HashStream putBooleanArray(boolean[] x);
+
+ @Override
+ HashStream putShort(short v);
+
+ @Override
+ HashStream putShorts(short[] x);
+
+ @Override
+ HashStream putShorts(short[] x, int off, int len);
+
+ @Override
+ HashStream putShortArray(short[] x);
+
+ @Override
+ HashStream putChar(char v);
+
+ @Override
+ HashStream putChars(char[] x);
+
+ @Override
+ HashStream putChars(char[] x, int off, int len);
+
+ @Override
+ HashStream putChars(CharSequence c);
+
+ @Override
+ HashStream putCharArray(char[] x);
+
+ @Override
+ HashStream putString(String s);
+
+ @Override
+ HashStream putInt(int v);
+
+ @Override
+ HashStream putInts(int[] x);
+
+ @Override
+ HashStream putInts(int[] x, int off, int len);
+
+ @Override
+ HashStream putIntArray(int[] x);
+
+ @Override
+ HashStream putLong(long v);
+
+ @Override
+ HashStream putLongs(long[] x);
+
+ @Override
+ HashStream putLongs(long[] x, int off, int len);
+
+ @Override
+ HashStream putLongArray(long[] x);
+
+ @Override
+ HashStream putFloat(float v);
+
+ @Override
+ HashStream putFloats(float[] x);
+
+ @Override
+ HashStream putFloats(float[] x, int off, int len);
+
+ @Override
+ HashStream putFloatArray(float[] x);
+
+ @Override
+ HashStream putDouble(double v);
+
+ @Override
+ HashStream putDoubles(double[] x);
+
+ @Override
+ HashStream putDoubles(double[] x, int off, int len);
+
+ @Override
+ HashStream putDoubleArray(double[] x);
+
+ @Override
+ HashStream putOptionalInt(OptionalInt v);
+
+ @Override
+ HashStream putOptionalLong(OptionalLong v);
+
+ @Override
+ HashStream putOptionalDouble(OptionalDouble v);
+
+ /**
+ * Resets the hash stream.
+ *
+ *
This allows to reuse this instance for new hash computations.
+ *
+ * @return this
+ */
+ HashStream reset();
+}
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/XXH3_64.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/XXH3_64.java
new file mode 100644
index 00000000..7e0be4b7
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/hashing/XXH3_64.java
@@ -0,0 +1,1001 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime.hashing;
+
+import static com.hedera.pbj.runtime.hashing.ByteArrayUtil.*;
+
+@SuppressWarnings({"DuplicatedCode", "NumericOverflow"})
+public class XXH3_64 {
+ private static final XXH3_64 DEFAULT_HASHER_INSTANCE = new XXH3_64(0);
+
+ public static long hash_xxh3_64(final byte[] bytes, int start, int length) {
+ return DEFAULT_HASHER_INSTANCE.hashBytesToLong(bytes, start, length);
+ }
+
+ private static final int BLOCK_LEN_EXP = 10;
+ private static final long SECRET_00 = 0xbe4ba423396cfeb8L;
+ private static final long SECRET_01 = 0x1cad21f72c81017cL;
+ private static final long SECRET_02 = 0xdb979083e96dd4deL;
+ private static final long SECRET_03 = 0x1f67b3b7a4a44072L;
+ private static final long SECRET_04 = 0x78e5c0cc4ee679cbL;
+ private static final long SECRET_05 = 0x2172ffcc7dd05a82L;
+ private static final long SECRET_06 = 0x8e2443f7744608b8L;
+ private static final long SECRET_07 = 0x4c263a81e69035e0L;
+ private static final long SECRET_08 = 0xcb00c391bb52283cL;
+ private static final long SECRET_09 = 0xa32e531b8b65d088L;
+ private static final long SECRET_10 = 0x4ef90da297486471L;
+ private static final long SECRET_11 = 0xd8acdea946ef1938L;
+ private static final long SECRET_12 = 0x3f349ce33f76faa8L;
+ private static final long SECRET_13 = 0x1d4f0bc7c7bbdcf9L;
+ private static final long SECRET_14 = 0x3159b4cd4be0518aL;
+ private static final long SECRET_15 = 0x647378d9c97e9fc8L;
+ private static final long SECRET_16 = 0xc3ebd33483acc5eaL;
+ private static final long SECRET_17 = 0xeb6313faffa081c5L;
+ private static final long SECRET_18 = 0x49daf0b751dd0d17L;
+ private static final long SECRET_19 = 0x9e68d429265516d3L;
+ private static final long SECRET_20 = 0xfca1477d58be162bL;
+ private static final long SECRET_21 = 0xce31d07ad1b8f88fL;
+ private static final long SECRET_22 = 0x280416958f3acb45L;
+ private static final long SECRET_23 = 0x7e404bbbcafbd7afL;
+ private static final long INIT_ACC_0 = 0x00000000C2B2AE3DL;
+ private static final long INIT_ACC_1 = 0x9E3779B185EBCA87L;
+ private static final long INIT_ACC_2 = 0xC2B2AE3D27D4EB4FL;
+ private static final long INIT_ACC_3 = 0x165667B19E3779F9L;
+ private static final long INIT_ACC_4 = 0x85EBCA77C2B2AE63L;
+ private static final long INIT_ACC_5 = 0x0000000085EBCA77L;
+ private static final long INIT_ACC_6 = 0x27D4EB2F165667C5L;
+ private static final long INIT_ACC_7 = 0x000000009E3779B1L;
+
+ private final long secret00;
+ private final long secret01;
+ private final long secret02;
+ private final long secret03;
+ private final long secret04;
+ private final long secret05;
+ private final long secret06;
+ private final long secret07;
+ private final long secret08;
+ private final long secret09;
+ private final long secret10;
+ private final long secret11;
+ private final long secret12;
+ private final long secret13;
+ private final long secret14;
+ private final long secret15;
+ private final long secret16;
+ private final long secret17;
+ private final long secret18;
+ private final long secret19;
+ private final long secret20;
+ private final long secret21;
+ private final long secret22;
+ private final long secret23;
+
+ private final long[] secret;
+
+ private final long secShift00;
+ private final long secShift01;
+ private final long secShift02;
+ private final long secShift03;
+ private final long secShift04;
+ private final long secShift05;
+ private final long secShift06;
+ private final long secShift07;
+ private final long secShift08;
+ private final long secShift09;
+ private final long secShift10;
+ private final long secShift11;
+
+ private final long secShift16;
+ private final long secShift17;
+ private final long secShift18;
+ private final long secShift19;
+ private final long secShift20;
+ private final long secShift21;
+ private final long secShift22;
+ private final long secShift23;
+
+ private final long secShiftFinal0;
+ private final long secShiftFinal1;
+ private final long secShiftFinal2;
+ private final long secShiftFinal3;
+ private final long secShiftFinal4;
+ private final long secShiftFinal5;
+ private final long secShiftFinal6;
+ private final long secShiftFinal7;
+ private final long secShift12;
+ private final long secShift13;
+ private final long secShift14;
+ private final long secShift15;
+ private final long bitflip00;
+ private final long bitflip12;
+ private final long bitflip34;
+ private final long bitflip56;
+ private final long hash0;
+
+ @SuppressWarnings("NumericOverflow")
+ private XXH3_64(long seed) {
+ this.secret00 = SECRET_00 + seed;
+ this.secret01 = SECRET_01 - seed;
+ this.secret02 = SECRET_02 + seed;
+ this.secret03 = SECRET_03 - seed;
+ this.secret04 = SECRET_04 + seed;
+ this.secret05 = SECRET_05 - seed;
+ this.secret06 = SECRET_06 + seed;
+ this.secret07 = SECRET_07 - seed;
+ this.secret08 = SECRET_08 + seed;
+ this.secret09 = SECRET_09 - seed;
+ this.secret10 = SECRET_10 + seed;
+ this.secret11 = SECRET_11 - seed;
+ this.secret12 = SECRET_12 + seed;
+ this.secret13 = SECRET_13 - seed;
+ this.secret14 = SECRET_14 + seed;
+ this.secret15 = SECRET_15 - seed;
+ this.secret16 = SECRET_16 + seed;
+ this.secret17 = SECRET_17 - seed;
+ this.secret18 = SECRET_18 + seed;
+ this.secret19 = SECRET_19 - seed;
+ this.secret20 = SECRET_20 + seed;
+ this.secret21 = SECRET_21 - seed;
+ this.secret22 = SECRET_22 + seed;
+ this.secret23 = SECRET_23 - seed;
+
+ this.secShift00 = (SECRET_00 >>> 24) + (SECRET_01 << 40) + seed;
+ this.secShift01 = (SECRET_01 >>> 24) + (SECRET_02 << 40) - seed;
+ this.secShift02 = (SECRET_02 >>> 24) + (SECRET_03 << 40) + seed;
+ this.secShift03 = (SECRET_03 >>> 24) + (SECRET_04 << 40) - seed;
+ this.secShift04 = (SECRET_04 >>> 24) + (SECRET_05 << 40) + seed;
+ this.secShift05 = (SECRET_05 >>> 24) + (SECRET_06 << 40) - seed;
+ this.secShift06 = (SECRET_06 >>> 24) + (SECRET_07 << 40) + seed;
+ this.secShift07 = (SECRET_07 >>> 24) + (SECRET_08 << 40) - seed;
+ this.secShift08 = (SECRET_08 >>> 24) + (SECRET_09 << 40) + seed;
+ this.secShift09 = (SECRET_09 >>> 24) + (SECRET_10 << 40) - seed;
+ this.secShift10 = (SECRET_10 >>> 24) + (SECRET_11 << 40) + seed;
+ this.secShift11 = (SECRET_11 >>> 24) + (SECRET_12 << 40) - seed;
+
+ this.secShift16 = secret15 >>> 8 | secret16 << 56;
+ this.secShift17 = secret16 >>> 8 | secret17 << 56;
+ this.secShift18 = secret17 >>> 8 | secret18 << 56;
+ this.secShift19 = secret18 >>> 8 | secret19 << 56;
+ this.secShift20 = secret19 >>> 8 | secret20 << 56;
+ this.secShift21 = secret20 >>> 8 | secret21 << 56;
+ this.secShift22 = secret21 >>> 8 | secret22 << 56;
+ this.secShift23 = secret22 >>> 8 | secret23 << 56;
+
+ this.secShiftFinal0 = secret01 >>> 24 | secret02 << 40;
+ this.secShiftFinal1 = secret02 >>> 24 | secret03 << 40;
+ this.secShiftFinal2 = secret03 >>> 24 | secret04 << 40;
+ this.secShiftFinal3 = secret04 >>> 24 | secret05 << 40;
+ this.secShiftFinal4 = secret05 >>> 24 | secret06 << 40;
+ this.secShiftFinal5 = secret06 >>> 24 | secret07 << 40;
+ this.secShiftFinal6 = secret07 >>> 24 | secret08 << 40;
+ this.secShiftFinal7 = secret08 >>> 24 | secret09 << 40;
+
+ this.secret = new long[] {
+ secret00, secret01, secret02, secret03, secret04, secret05, secret06, secret07,
+ secret08, secret09, secret10, secret11, secret12, secret13, secret14, secret15,
+ secret16, secret17, secret18, secret19, secret20, secret21, secret22, secret23
+ };
+
+ this.secShift12 = (SECRET_12 >>> 24) + (SECRET_13 << 40) + seed;
+ this.secShift13 = (SECRET_13 >>> 24) + (SECRET_14 << 40) - seed;
+ this.secShift14 = (SECRET_14 >>> 56) + (SECRET_15 << 8) + seed;
+ this.secShift15 = (SECRET_15 >>> 56) + (SECRET_16 << 8) - seed;
+
+ this.bitflip00 = ((SECRET_00 >>> 32) ^ (SECRET_00 & 0xFFFFFFFFL)) + seed;
+ this.bitflip12 = (SECRET_01 ^ SECRET_02) - (seed ^ Long.reverseBytes(seed & 0xFFFFFFFFL));
+ this.bitflip34 = (SECRET_03 ^ SECRET_04) + seed;
+ this.bitflip56 = (SECRET_05 ^ SECRET_06) - seed;
+
+ this.hash0 = avalanche64(seed ^ (SECRET_07 ^ SECRET_08));
+ }
+
+ private static long rrmxmx(long h64, final long length) {
+ h64 ^= Long.rotateLeft(h64, 49) ^ Long.rotateLeft(h64, 24);
+ h64 *= 0x9FB21C651E98DF25L;
+ h64 ^= (h64 >>> 35) + length;
+ h64 *= 0x9FB21C651E98DF25L;
+ return h64 ^ (h64 >>> 28);
+ }
+
+ private static long mix16B(final byte[] input, final int offIn, final long sec0, final long sec1) {
+ long lo = getLong(input, offIn);
+ long hi = getLong(input, offIn + 8);
+ return mix2Accs(lo, hi, sec0, sec1);
+ }
+
+ private static long mix16B(final CharSequence input, final int offIn, final long sec0, final long sec1) {
+ long lo = getLong(input, offIn);
+ long hi = getLong(input, offIn + 4);
+ return mix2Accs(lo, hi, sec0, sec1);
+ }
+
+ private static long avalanche64(long h64) {
+ h64 ^= h64 >>> 33;
+ h64 *= INIT_ACC_2;
+ h64 ^= h64 >>> 29;
+ h64 *= INIT_ACC_3;
+ return h64 ^ (h64 >>> 32);
+ }
+
+ private static long avalanche3(long h64) {
+ h64 ^= h64 >>> 37;
+ h64 *= 0x165667919E3779F9L;
+ return h64 ^ (h64 >>> 32);
+ }
+
+ private static long mix2Accs(final long lh, final long rh, long sec0, long sec8) {
+ return mix(lh ^ sec0, rh ^ sec8);
+ }
+
+ private static long contrib(long a, long b) {
+ long k = a ^ b;
+ return (0xFFFFFFFFL & k) * (k >>> 32);
+ }
+
+ private static long mixAcc(long acc, long sec) {
+ return (acc ^ (acc >>> 47) ^ sec) * INIT_ACC_7;
+ }
+
+ private static long mix(long a, long b) {
+ long x = a * b;
+ long y = Math.unsignedMultiplyHigh(a, b);
+ return x ^ y;
+ }
+
+ /**
+ * Starts a hash stream.
+ *
+ * @return a new {@link HashStream} instance
+ */
+ public HashStream hashStream() {
+ return new HashStreamImplBase();
+ }
+
+ /**
+ * Hashes a byte array to a 64-bit {@code long} value.
+ *
+ *
Equivalent to {@code hashToLong(input, (b, f) -> f.putBytes(b, off, len))}.
+ *
+ * @param input the byte array
+ * @param off the offset
+ * @param length the length
+ * @return the hash value
+ */
+ public long hashBytesToLong(final byte[] input, final int off, final int length) {
+ if (length <= 16) {
+ if (length > 8) {
+ long lo = getLong(input, off) ^ bitflip34;
+ long hi = getLong(input, off + length - 8) ^ bitflip56;
+ long acc = length + Long.reverseBytes(lo) + hi + mix(lo, hi);
+ return avalanche3(acc);
+ }
+ if (length >= 4) {
+ long input1 = getInt(input, off);
+ long input2 = getInt(input, off + length - 4);
+ long keyed = (input2 & 0xFFFFFFFFL) ^ (input1 << 32) ^ bitflip12;
+ return XXH3_64.rrmxmx(keyed, length);
+ }
+ if (length != 0) {
+ int c1 = input[off] & 0xFF;
+ int c2 = input[off + (length >> 1)];
+ int c3 = input[off + length - 1] & 0xFF;
+ long combined = ((c1 << 16) | (c2 << 24) | c3 | ((long) length << 8)) & 0xFFFFFFFFL;
+ return avalanche64(combined ^ bitflip00);
+ }
+ return hash0;
+ }
+ if (length <= 128) {
+ long acc = length * INIT_ACC_1;
+
+ if (length > 32) {
+ if (length > 64) {
+ if (length > 96) {
+ acc += XXH3_64.mix16B(input, off + 48, secret12, secret13);
+ acc += XXH3_64.mix16B(input, off + length - 64, secret14, secret15);
+ }
+ acc += XXH3_64.mix16B(input, off + 32, secret08, secret09);
+ acc += XXH3_64.mix16B(input, off + length - 48, secret10, secret11);
+ }
+ acc += XXH3_64.mix16B(input, off + 16, secret04, secret05);
+ acc += XXH3_64.mix16B(input, off + length - 32, secret06, secret07);
+ }
+ acc += XXH3_64.mix16B(input, off, secret00, secret01);
+ acc += XXH3_64.mix16B(input, off + length - 16, secret02, secret03);
+
+ return avalanche3(acc);
+ }
+ if (length <= 240) {
+ long acc = length * INIT_ACC_1;
+ acc += XXH3_64.mix16B(input, off, secret00, secret01);
+ acc += XXH3_64.mix16B(input, off + 16, secret02, secret03);
+ acc += XXH3_64.mix16B(input, off + 16 * 2, secret04, secret05);
+ acc += XXH3_64.mix16B(input, off + 16 * 3, secret06, secret07);
+ acc += XXH3_64.mix16B(input, off + 16 * 4, secret08, secret09);
+ acc += XXH3_64.mix16B(input, off + 16 * 5, secret10, secret11);
+ acc += XXH3_64.mix16B(input, off + 16 * 6, secret12, secret13);
+ acc += XXH3_64.mix16B(input, off + 16 * 7, secret14, secret15);
+
+ acc = avalanche3(acc);
+
+ if (length >= 144) {
+ acc += XXH3_64.mix16B(input, off + 128, secShift00, secShift01);
+ if (length >= 160) {
+ acc += XXH3_64.mix16B(input, off + 144, secShift02, secShift03);
+ if (length >= 176) {
+ acc += XXH3_64.mix16B(input, off + 160, secShift04, secShift05);
+ if (length >= 192) {
+ acc += XXH3_64.mix16B(input, off + 176, secShift06, secShift07);
+ if (length >= 208) {
+ acc += XXH3_64.mix16B(input, off + 192, secShift08, secShift09);
+ if (length >= 224) {
+ acc += XXH3_64.mix16B(input, off + 208, secShift10, secShift11);
+ if (length >= 240) acc += XXH3_64.mix16B(input, off + 224, secShift12, secShift13);
+ }
+ }
+ }
+ }
+ }
+ }
+ acc += XXH3_64.mix16B(input, off + length - 16, secShift14, secShift15);
+ return avalanche3(acc);
+ }
+
+ long acc0 = INIT_ACC_0;
+ long acc1 = INIT_ACC_1;
+ long acc2 = INIT_ACC_2;
+ long acc3 = INIT_ACC_3;
+ long acc4 = INIT_ACC_4;
+ long acc5 = INIT_ACC_5;
+ long acc6 = INIT_ACC_6;
+ long acc7 = INIT_ACC_7;
+
+ final int nbBlocks = (length - 1) >>> BLOCK_LEN_EXP;
+ for (int n = 0; n < nbBlocks; n++) {
+ final int offBlock = off + (n << BLOCK_LEN_EXP);
+ for (int s = 0; s < 16; s += 1) {
+ int offStripe = offBlock + (s << 6);
+
+ long b0 = getLong(input, offStripe);
+ long b1 = getLong(input, offStripe + 8);
+ long b2 = getLong(input, offStripe + 8 * 2);
+ long b3 = getLong(input, offStripe + 8 * 3);
+ long b4 = getLong(input, offStripe + 8 * 4);
+ long b5 = getLong(input, offStripe + 8 * 5);
+ long b6 = getLong(input, offStripe + 8 * 6);
+ long b7 = getLong(input, offStripe + 8 * 7);
+
+ acc0 += b1 + contrib(b0, secret[s]);
+ acc1 += b0 + contrib(b1, secret[s + 1]);
+ acc2 += b3 + contrib(b2, secret[s + 2]);
+ acc3 += b2 + contrib(b3, secret[s + 3]);
+ acc4 += b5 + contrib(b4, secret[s + 4]);
+ acc5 += b4 + contrib(b5, secret[s + 5]);
+ acc6 += b7 + contrib(b6, secret[s + 6]);
+ acc7 += b6 + contrib(b7, secret[s + 7]);
+ }
+
+ acc0 = mixAcc(acc0, secret16);
+ acc1 = mixAcc(acc1, secret17);
+ acc2 = mixAcc(acc2, secret18);
+ acc3 = mixAcc(acc3, secret19);
+ acc4 = mixAcc(acc4, secret20);
+ acc5 = mixAcc(acc5, secret21);
+ acc6 = mixAcc(acc6, secret22);
+ acc7 = mixAcc(acc7, secret23);
+ }
+
+ final int nbStripes = ((length - 1) - (nbBlocks << BLOCK_LEN_EXP)) >>> 6;
+ final int offBlock = off + (nbBlocks << BLOCK_LEN_EXP);
+ for (int s = 0; s < nbStripes; s++) {
+ int offStripe = offBlock + (s << 6);
+
+ long b0 = getLong(input, offStripe);
+ long b1 = getLong(input, offStripe + 8);
+ long b2 = getLong(input, offStripe + 8 * 2);
+ long b3 = getLong(input, offStripe + 8 * 3);
+ long b4 = getLong(input, offStripe + 8 * 4);
+ long b5 = getLong(input, offStripe + 8 * 5);
+ long b6 = getLong(input, offStripe + 8 * 6);
+ long b7 = getLong(input, offStripe + 8 * 7);
+
+ acc0 += b1 + contrib(b0, secret[s]);
+ acc1 += b0 + contrib(b1, secret[s + 1]);
+ acc2 += b3 + contrib(b2, secret[s + 2]);
+ acc3 += b2 + contrib(b3, secret[s + 3]);
+ acc4 += b5 + contrib(b4, secret[s + 4]);
+ acc5 += b4 + contrib(b5, secret[s + 5]);
+ acc6 += b7 + contrib(b6, secret[s + 6]);
+ acc7 += b6 + contrib(b7, secret[s + 7]);
+ }
+
+ {
+ int offStripe = off + length - 64;
+
+ long b0 = getLong(input, offStripe);
+ long b1 = getLong(input, offStripe + 8);
+ long b2 = getLong(input, offStripe + 8 * 2);
+ long b3 = getLong(input, offStripe + 8 * 3);
+ long b4 = getLong(input, offStripe + 8 * 4);
+ long b5 = getLong(input, offStripe + 8 * 5);
+ long b6 = getLong(input, offStripe + 8 * 6);
+ long b7 = getLong(input, offStripe + 8 * 7);
+
+ acc0 += b1 + contrib(b0, secShift16);
+ acc1 += b0 + contrib(b1, secShift17);
+ acc2 += b3 + contrib(b2, secShift18);
+ acc3 += b2 + contrib(b3, secShift19);
+ acc4 += b5 + contrib(b4, secShift20);
+ acc5 += b4 + contrib(b5, secShift21);
+ acc6 += b7 + contrib(b6, secShift22);
+ acc7 += b6 + contrib(b7, secShift23);
+ }
+
+ return finalizeHash(length, acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7);
+ }
+
+ private long finalizeHash(
+ long length, long acc0, long acc1, long acc2, long acc3, long acc4, long acc5, long acc6, long acc7) {
+
+ long result64 = length * INIT_ACC_1
+ + mix2Accs(acc0, acc1, secShiftFinal0, secShiftFinal1)
+ + mix2Accs(acc2, acc3, secShiftFinal2, secShiftFinal3)
+ + mix2Accs(acc4, acc5, secShiftFinal4, secShiftFinal5)
+ + mix2Accs(acc6, acc7, secShiftFinal6, secShiftFinal7);
+
+ return avalanche3(result64);
+ }
+
+ public long hashCharsToLong(CharSequence charSequence) {
+
+ int len = charSequence.length();
+
+ if (len <= 8) {
+ if (len > 4) {
+ long lo = getLong(charSequence, 0) ^ bitflip34;
+ long hi = getLong(charSequence, len - 4) ^ bitflip56;
+ long acc = (len << 1) + Long.reverseBytes(lo) + hi + mix(lo, hi);
+ return avalanche3(acc);
+ }
+ if (len >= 2) {
+ long input1 = getInt(charSequence, 0);
+ long input2 = getInt(charSequence, len - 2);
+ long keyed = (input2 & 0xFFFFFFFFL) ^ (input1 << 32) ^ bitflip12;
+ return XXH3_64.rrmxmx(keyed, len << 1);
+ }
+ if (len != 0) {
+ long c = charSequence.charAt(0);
+ long combined = (c << 16) | (c >>> 8) | 512L;
+ return avalanche64(combined ^ bitflip00);
+ }
+ return hash0;
+ }
+ if (len <= 64) {
+ long acc = len * (INIT_ACC_1 << 1);
+
+ if (len > 16) {
+ if (len > 32) {
+ if (len > 48) {
+ acc += XXH3_64.mix16B(charSequence, 24, secret12, secret13);
+ acc += XXH3_64.mix16B(charSequence, len - 32, secret14, secret15);
+ }
+ acc += XXH3_64.mix16B(charSequence, 16, secret08, secret09);
+ acc += XXH3_64.mix16B(charSequence, len - 24, secret10, secret11);
+ }
+ acc += XXH3_64.mix16B(charSequence, 8, secret04, secret05);
+ acc += XXH3_64.mix16B(charSequence, len - 16, secret06, secret07);
+ }
+ acc += XXH3_64.mix16B(charSequence, 0, secret00, secret01);
+ acc += XXH3_64.mix16B(charSequence, len - 8, secret02, secret03);
+
+ return avalanche3(acc);
+ }
+ if (len <= 120) {
+ long acc = len * (INIT_ACC_1 << 1);
+ acc += XXH3_64.mix16B(charSequence, 0, secret00, secret01);
+ acc += XXH3_64.mix16B(charSequence, 8, secret02, secret03);
+ acc += XXH3_64.mix16B(charSequence, 16, secret04, secret05);
+ acc += XXH3_64.mix16B(charSequence, 24, secret06, secret07);
+ acc += XXH3_64.mix16B(charSequence, 32, secret08, secret09);
+ acc += XXH3_64.mix16B(charSequence, 40, secret10, secret11);
+ acc += XXH3_64.mix16B(charSequence, 48, secret12, secret13);
+ acc += XXH3_64.mix16B(charSequence, 56, secret14, secret15);
+
+ acc = avalanche3(acc);
+
+ if (len >= 72) {
+ acc += XXH3_64.mix16B(charSequence, 64, secShift00, secShift01);
+ if (len >= 80) {
+ acc += XXH3_64.mix16B(charSequence, 72, secShift02, secShift03);
+ if (len >= 88) {
+ acc += XXH3_64.mix16B(charSequence, 80, secShift04, secShift05);
+ if (len >= 96) {
+ acc += XXH3_64.mix16B(charSequence, 88, secShift06, secShift07);
+ if (len >= 104) {
+ acc += XXH3_64.mix16B(charSequence, 96, secShift08, secShift09);
+ if (len >= 112) {
+ acc += XXH3_64.mix16B(charSequence, 104, secShift10, secShift11);
+ if (len >= 120) acc += XXH3_64.mix16B(charSequence, 112, secShift12, secShift13);
+ }
+ }
+ }
+ }
+ }
+ }
+ acc += XXH3_64.mix16B(charSequence, len - 8, secShift14, secShift15);
+ return avalanche3(acc);
+ }
+
+ long acc0 = INIT_ACC_0;
+ long acc1 = INIT_ACC_1;
+ long acc2 = INIT_ACC_2;
+ long acc3 = INIT_ACC_3;
+ long acc4 = INIT_ACC_4;
+ long acc5 = INIT_ACC_5;
+ long acc6 = INIT_ACC_6;
+ long acc7 = INIT_ACC_7;
+
+ final int nbBlocks = (len - 1) >>> (BLOCK_LEN_EXP - 1);
+ for (int n = 0; n < nbBlocks; n++) {
+ final int offBlock = n << (BLOCK_LEN_EXP - 1);
+ for (int s = 0; s < 16; s += 1) {
+ int offStripe = offBlock + (s << 5);
+
+ long b0 = getLong(charSequence, offStripe);
+ long b1 = getLong(charSequence, offStripe + 4);
+ long b2 = getLong(charSequence, offStripe + 4 * 2);
+ long b3 = getLong(charSequence, offStripe + 4 * 3);
+ long b4 = getLong(charSequence, offStripe + 4 * 4);
+ long b5 = getLong(charSequence, offStripe + 4 * 5);
+ long b6 = getLong(charSequence, offStripe + 4 * 6);
+ long b7 = getLong(charSequence, offStripe + 4 * 7);
+
+ acc0 += b1 + contrib(b0, secret[s]);
+ acc1 += b0 + contrib(b1, secret[s + 1]);
+ acc2 += b3 + contrib(b2, secret[s + 2]);
+ acc3 += b2 + contrib(b3, secret[s + 3]);
+ acc4 += b5 + contrib(b4, secret[s + 4]);
+ acc5 += b4 + contrib(b5, secret[s + 5]);
+ acc6 += b7 + contrib(b6, secret[s + 6]);
+ acc7 += b6 + contrib(b7, secret[s + 7]);
+ }
+
+ acc0 = mixAcc(acc0, secret16);
+ acc1 = mixAcc(acc1, secret17);
+ acc2 = mixAcc(acc2, secret18);
+ acc3 = mixAcc(acc3, secret19);
+ acc4 = mixAcc(acc4, secret20);
+ acc5 = mixAcc(acc5, secret21);
+ acc6 = mixAcc(acc6, secret22);
+ acc7 = mixAcc(acc7, secret23);
+ }
+
+ final int nbStripes = ((len - 1) - (nbBlocks << (BLOCK_LEN_EXP - 1))) >>> 5;
+ final int offBlock = nbBlocks << (BLOCK_LEN_EXP - 1);
+ for (int s = 0; s < nbStripes; s++) {
+ int offStripe = offBlock + (s << 5);
+
+ long b0 = getLong(charSequence, offStripe);
+ long b1 = getLong(charSequence, offStripe + 4);
+ long b2 = getLong(charSequence, offStripe + 4 * 2);
+ long b3 = getLong(charSequence, offStripe + 4 * 3);
+ long b4 = getLong(charSequence, offStripe + 4 * 4);
+ long b5 = getLong(charSequence, offStripe + 4 * 5);
+ long b6 = getLong(charSequence, offStripe + 4 * 6);
+ long b7 = getLong(charSequence, offStripe + 4 * 7);
+
+ acc0 += b1 + contrib(b0, secret[s]);
+ acc1 += b0 + contrib(b1, secret[s + 1]);
+ acc2 += b3 + contrib(b2, secret[s + 2]);
+ acc3 += b2 + contrib(b3, secret[s + 3]);
+ acc4 += b5 + contrib(b4, secret[s + 4]);
+ acc5 += b4 + contrib(b5, secret[s + 5]);
+ acc6 += b7 + contrib(b6, secret[s + 6]);
+ acc7 += b6 + contrib(b7, secret[s + 7]);
+ }
+
+ {
+ int offStripe = len - 32;
+
+ long b0 = getLong(charSequence, offStripe);
+ long b1 = getLong(charSequence, offStripe + 4);
+ long b2 = getLong(charSequence, offStripe + 4 * 2);
+ long b3 = getLong(charSequence, offStripe + 4 * 3);
+ long b4 = getLong(charSequence, offStripe + 4 * 4);
+ long b5 = getLong(charSequence, offStripe + 4 * 5);
+ long b6 = getLong(charSequence, offStripe + 4 * 6);
+ long b7 = getLong(charSequence, offStripe + 4 * 7);
+
+ acc0 += b1 + contrib(b0, secShift16);
+ acc1 += b0 + contrib(b1, secShift17);
+ acc2 += b3 + contrib(b2, secShift18);
+ acc3 += b2 + contrib(b3, secShift19);
+ acc4 += b5 + contrib(b4, secShift20);
+ acc5 += b4 + contrib(b5, secShift21);
+ acc6 += b7 + contrib(b6, secShift22);
+ acc7 += b6 + contrib(b7, secShift23);
+ }
+
+ return finalizeHash((long) len << 1, acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7);
+ }
+
+ private class HashStreamImplBase implements HashStream, AbstractHashStream {
+ private static final int BULK_SIZE = 256;
+ private static final int BULK_SIZE_HALF = 128;
+ private static final int BULK_SIZE_MASK = BULK_SIZE - 1;
+
+ private long acc0 = INIT_ACC_0;
+ private long acc1 = INIT_ACC_1;
+ private long acc2 = INIT_ACC_2;
+ private long acc3 = INIT_ACC_3;
+ private long acc4 = INIT_ACC_4;
+ private long acc5 = INIT_ACC_5;
+ private long acc6 = INIT_ACC_6;
+ private long acc7 = INIT_ACC_7;
+ private final byte[] buffer = new byte[BULK_SIZE + 8];
+ private int offset = 0;
+ private long byteCount = 0;
+
+ private void putByteImpl(byte v) {
+ if (offset >= BULK_SIZE) {
+ processBuffer();
+ offset -= BULK_SIZE;
+ }
+ buffer[offset] = v;
+ offset += 1;
+ byteCount += 1;
+ }
+
+ private void putShortImpl(short v) {
+ setShort(buffer, offset, v);
+ if (offset >= BULK_SIZE - 1) {
+ processBuffer();
+ offset -= BULK_SIZE;
+ setShort(buffer, 0, (short) (v >>> (-offset << 3)));
+ }
+ offset += 2;
+ byteCount += 2;
+ }
+
+ private void putCharImpl(char v) {
+ setChar(buffer, offset, v);
+ if (offset >= BULK_SIZE - 1) {
+ processBuffer();
+ offset -= BULK_SIZE;
+ setChar(buffer, 0, (char) (v >>> (-offset << 3)));
+ }
+ offset += 2;
+ byteCount += 2;
+ }
+
+ private void putIntImpl(int v) {
+ setInt(buffer, offset, v);
+ if (offset >= BULK_SIZE - 3) {
+ processBuffer();
+ offset -= BULK_SIZE;
+ setInt(buffer, 0, v >>> (-offset << 3));
+ }
+ offset += 4;
+ byteCount += 4;
+ }
+
+ private void putLongImpl(long v) {
+ setLong(buffer, offset, v);
+ if (offset >= BULK_SIZE - 7) {
+ processBuffer();
+ offset -= BULK_SIZE;
+ setLong(buffer, 0, v >>> (-offset << 3));
+ }
+ offset += 8;
+ byteCount += 8;
+ }
+
+ private void putBytesImpl(byte[] b, int off, final int len) {
+ int remaining = len;
+ final int x = BULK_SIZE - offset;
+ if (len > x) {
+ int s = (int) ((byteCount - 1) >>> 6) & 12;
+ if (offset > 0) {
+ System.arraycopy(b, off, buffer, offset, x);
+ processBuffer(0, buffer, s);
+ offset = 0;
+ off += x;
+ remaining -= x;
+ }
+ if (remaining > BULK_SIZE) {
+ do {
+ s += 4;
+ s &= 12;
+ processBuffer(off, b, s);
+ off += BULK_SIZE;
+ remaining -= BULK_SIZE;
+ } while (remaining > BULK_SIZE);
+ if (remaining < 64) {
+ int l = 64 - remaining;
+ System.arraycopy(b, off - l, buffer, BULK_SIZE - l, l);
+ }
+ }
+ }
+ System.arraycopy(b, off, buffer, offset, remaining);
+ offset += remaining;
+ byteCount += len;
+ }
+
+ private void putCharsImpl(CharSequence c) {
+ int off = 0;
+ int remaining = c.length();
+ final int x = BULK_SIZE_HALF - (offset >>> 1);
+ if ((offset & 1) == 0) {
+ if (c.length() > x) {
+ int s = (int) ((byteCount - 1) >>> 6) & 12;
+ if (offset > 0) {
+ copyCharsToByteArray(c, 0, buffer, offset, x);
+ processBuffer(0, buffer, s);
+ offset = 0;
+ off += x;
+ remaining -= x;
+ }
+ if (remaining > BULK_SIZE_HALF) {
+ do {
+ s += 4;
+ s &= 12;
+ processBuffer(off, c, s);
+ off += BULK_SIZE_HALF;
+ remaining -= BULK_SIZE_HALF;
+ } while (remaining > BULK_SIZE_HALF);
+ if (remaining < 32) {
+ int l = 32 - remaining;
+ copyCharsToByteArray(c, off - l, buffer, BULK_SIZE - (l << 1), l);
+ }
+ }
+ }
+ } else {
+ if (c.length() >= x) {
+ long extraByte;
+ int s = (int) ((byteCount - 1) >>> 6) & 12;
+ copyCharsToByteArray(c, 0, buffer, offset, x);
+ extraByte = buffer[BULK_SIZE] & 0xFFL;
+ processBuffer(0, buffer, s);
+ offset = 1;
+ off += x;
+ remaining -= x;
+ if (remaining >= BULK_SIZE_HALF) {
+ do {
+ s += 4;
+ s &= 12;
+ extraByte = processBuffer(off, c, s, extraByte);
+ off += BULK_SIZE_HALF;
+ remaining -= BULK_SIZE_HALF;
+ } while (remaining >= BULK_SIZE_HALF);
+ if (remaining < 32) {
+ int l = 32 - remaining;
+ copyCharsToByteArray(c, off - l, buffer, BULK_SIZE + 1 - (l << 1), l);
+ }
+ }
+ buffer[0] = (byte) extraByte;
+ }
+ }
+ copyCharsToByteArray(c, off, buffer, offset, remaining);
+ offset += remaining << 1;
+ byteCount += (long) c.length() << 1;
+ }
+
+ protected void resetImpl() {
+ acc0 = INIT_ACC_0;
+ acc1 = INIT_ACC_1;
+ acc2 = INIT_ACC_2;
+ acc3 = INIT_ACC_3;
+ acc4 = INIT_ACC_4;
+ acc5 = INIT_ACC_5;
+ acc6 = INIT_ACC_6;
+ acc7 = INIT_ACC_7;
+ offset = 0;
+ byteCount = 0;
+ }
+
+ private void processBuffer() {
+ int s = (int) ((byteCount - 1) >>> 6) & 12;
+ processBuffer(0, buffer, s);
+ }
+
+ private void mixAcc() {
+ acc0 = XXH3_64.mixAcc(acc0, secret16);
+ acc1 = XXH3_64.mixAcc(acc1, secret17);
+ acc2 = XXH3_64.mixAcc(acc2, secret18);
+ acc3 = XXH3_64.mixAcc(acc3, secret19);
+ acc4 = XXH3_64.mixAcc(acc4, secret20);
+ acc5 = XXH3_64.mixAcc(acc5, secret21);
+ acc6 = XXH3_64.mixAcc(acc6, secret22);
+ acc7 = XXH3_64.mixAcc(acc7, secret23);
+ }
+
+ private void processBuffer(int off, byte[] buffer, int s) {
+ for (int i = 0; i < 4; ++i) {
+ int o = off + (i << 6);
+ long b0 = getLong(buffer, o);
+ long b1 = getLong(buffer, o + 8);
+ long b2 = getLong(buffer, o + 8 * 2);
+ long b3 = getLong(buffer, o + 8 * 3);
+ long b4 = getLong(buffer, o + 8 * 4);
+ long b5 = getLong(buffer, o + 8 * 5);
+ long b6 = getLong(buffer, o + 8 * 6);
+ long b7 = getLong(buffer, o + 8 * 7);
+ processBuffer(b0, b1, b2, b3, b4, b5, b6, b7, s + i);
+ }
+ if (s == 12) {
+ mixAcc();
+ }
+ }
+
+ private void processBuffer(int off, CharSequence c, int s) {
+ for (int i = 0; i < 4; ++i) {
+ int o = off + (i << 5);
+ long b0 = getLong(c, o);
+ long b1 = getLong(c, o + 4);
+ long b2 = getLong(c, o + 4 * 2);
+ long b3 = getLong(c, o + 4 * 3);
+ long b4 = getLong(c, o + 4 * 4);
+ long b5 = getLong(c, o + 4 * 5);
+ long b6 = getLong(c, o + 4 * 6);
+ long b7 = getLong(c, o + 4 * 7);
+ processBuffer(b0, b1, b2, b3, b4, b5, b6, b7, s + i);
+ }
+ if (s == 12) {
+ mixAcc();
+ }
+ }
+
+ private long processBuffer(int off, CharSequence c, int s, long extraByte) {
+
+ for (int i = 0; i < 4; ++i) {
+ int o = off + (i << 5);
+
+ long b0 = getLong(c, o);
+ long b1 = getLong(c, o + 4);
+ long b2 = getLong(c, o + 4 * 2);
+ long b3 = getLong(c, o + 4 * 3);
+ long b4 = getLong(c, o + 4 * 4);
+ long b5 = getLong(c, o + 4 * 5);
+ long b6 = getLong(c, o + 4 * 6);
+ long b7 = getLong(c, o + 4 * 7);
+
+ long y = b7 >>> 56;
+ b7 = (b6 >>> 56) | (b7 << 8);
+ b6 = (b5 >>> 56) | (b6 << 8);
+ b5 = (b4 >>> 56) | (b5 << 8);
+ b4 = (b3 >>> 56) | (b4 << 8);
+ b3 = (b2 >>> 56) | (b3 << 8);
+ b2 = (b1 >>> 56) | (b2 << 8);
+ b1 = (b0 >>> 56) | (b1 << 8);
+ b0 = extraByte | (b0 << 8);
+ extraByte = y;
+
+ processBuffer(b0, b1, b2, b3, b4, b5, b6, b7, s + i);
+ }
+ if (s == 12) {
+ mixAcc();
+ }
+
+ return extraByte;
+ }
+
+ private void processBuffer(long b0, long b1, long b2, long b3, long b4, long b5, long b6, long b7, int s) {
+ acc0 += b1 + contrib(b0, secret[s]);
+ acc1 += b0 + contrib(b1, secret[s + 1]);
+ acc2 += b3 + contrib(b2, secret[s + 2]);
+ acc3 += b2 + contrib(b3, secret[s + 3]);
+ acc4 += b5 + contrib(b4, secret[s + 4]);
+ acc5 += b4 + contrib(b5, secret[s + 5]);
+ acc6 += b7 + contrib(b6, secret[s + 6]);
+ acc7 += b6 + contrib(b7, secret[s + 7]);
+ }
+
+ public long getAsLong() {
+ if (byteCount >= 0 && byteCount <= BULK_SIZE) {
+ return hashBytesToLong(buffer, 0, (int) byteCount);
+ }
+ setLong(buffer, BULK_SIZE, getLong(buffer, 0));
+
+ long acc0Loc = acc0;
+ long acc1Loc = acc1;
+ long acc2Loc = acc2;
+ long acc3Loc = acc3;
+ long acc4Loc = acc4;
+ long acc5Loc = acc5;
+ long acc6Loc = acc6;
+ long acc7Loc = acc7;
+
+ for (int off = 0, s = (((int) byteCount - 1) >>> 6) & 12;
+ off + 64 <= (((int) byteCount - 1) & BULK_SIZE_MASK);
+ off += 64, s += 1) {
+
+ long b0 = getLong(buffer, off);
+ long b1 = getLong(buffer, off + 8);
+ long b2 = getLong(buffer, off + 8 * 2);
+ long b3 = getLong(buffer, off + 8 * 3);
+ long b4 = getLong(buffer, off + 8 * 4);
+ long b5 = getLong(buffer, off + 8 * 5);
+ long b6 = getLong(buffer, off + 8 * 6);
+ long b7 = getLong(buffer, off + 8 * 7);
+
+ acc0Loc += b1 + contrib(b0, secret[s]);
+ acc1Loc += b0 + contrib(b1, secret[s + 1]);
+ acc2Loc += b3 + contrib(b2, secret[s + 2]);
+ acc3Loc += b2 + contrib(b3, secret[s + 3]);
+ acc4Loc += b5 + contrib(b4, secret[s + 4]);
+ acc5Loc += b4 + contrib(b5, secret[s + 5]);
+ acc6Loc += b7 + contrib(b6, secret[s + 6]);
+ acc7Loc += b6 + contrib(b7, secret[s + 7]);
+ }
+
+ {
+ long b0 = getLong(buffer, (offset - (64)) & BULK_SIZE_MASK);
+ long b1 = getLong(buffer, (offset - (64 - 8)) & BULK_SIZE_MASK);
+ long b2 = getLong(buffer, (offset - (64 - 8 * 2)) & BULK_SIZE_MASK);
+ long b3 = getLong(buffer, (offset - (64 - 8 * 3)) & BULK_SIZE_MASK);
+ long b4 = getLong(buffer, (offset - (64 - 8 * 4)) & BULK_SIZE_MASK);
+ long b5 = getLong(buffer, (offset - (64 - 8 * 5)) & BULK_SIZE_MASK);
+ long b6 = getLong(buffer, (offset - (64 - 8 * 6)) & BULK_SIZE_MASK);
+ long b7 = getLong(buffer, (offset - (64 - 8 * 7)) & BULK_SIZE_MASK);
+
+ acc0Loc += b1 + contrib(b0, secShift16);
+ acc1Loc += b0 + contrib(b1, secShift17);
+ acc2Loc += b3 + contrib(b2, secShift18);
+ acc3Loc += b2 + contrib(b3, secShift19);
+ acc4Loc += b5 + contrib(b4, secShift20);
+ acc5Loc += b4 + contrib(b5, secShift21);
+ acc6Loc += b7 + contrib(b6, secShift22);
+ acc7Loc += b6 + contrib(b7, secShift23);
+ }
+
+ return finalizeHash(byteCount, acc0Loc, acc1Loc, acc2Loc, acc3Loc, acc4Loc, acc5Loc, acc6Loc, acc7Loc);
+ }
+
+ @Override
+ public HashStream putByte(byte v) {
+ putByteImpl(v);
+ return this;
+ }
+
+ @Override
+ public HashStream putShort(short v) {
+ putShortImpl(v);
+ return this;
+ }
+
+ @Override
+ public HashStream putChar(char v) {
+ putCharImpl(v);
+ return this;
+ }
+
+ @Override
+ public HashStream putInt(int v) {
+ putIntImpl(v);
+ return this;
+ }
+
+ @Override
+ public HashStream putLong(long v) {
+ putLongImpl(v);
+ return this;
+ }
+
+ @Override
+ public HashStream putBytes(byte[] b, int off, final int len) {
+ putBytesImpl(b, off, len);
+ return this;
+ }
+
+ @Override
+ public HashStream putChars(CharSequence c) {
+ putCharsImpl(c);
+ return this;
+ }
+
+ @Override
+ public HashStream reset() {
+ resetImpl();
+ return this;
+ }
+ }
+}
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/UnsafeUtils.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/UnsafeUtils.java
index aa7a4bc2..0a830fa4 100644
--- a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/UnsafeUtils.java
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/UnsafeUtils.java
@@ -11,7 +11,8 @@
/**
* A set of utility methods on top of sun.misc.Unsafe
*/
-public class UnsafeUtils {
+@SuppressWarnings("GrazieInspection")
+public final class UnsafeUtils {
private static final Unsafe UNSAFE;
@@ -19,7 +20,7 @@ public class UnsafeUtils {
* Java and PBJ use BIG_ENDIAN, while native byte order used by Unsafe may or may not
* be BIG_ENDIAN. This flag indicates that if they don't match
*/
- private static final boolean NEED_CHANGE_BYTE_ORDER;
+ private static final boolean MACHINE_IS_LITTLE_ENDIAN;
/**
* Field offset of the byte[] class
@@ -37,7 +38,7 @@ public class UnsafeUtils {
final Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
UNSAFE = (Unsafe) theUnsafeField.get(null);
- NEED_CHANGE_BYTE_ORDER = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
+ MACHINE_IS_LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
BYTE_ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class);
final Field addressField = Buffer.class.getDeclaredField("address");
DIRECT_BYTEBUFFER_ADDRESS_OFFSET = UNSAFE.objectFieldOffset(addressField);
@@ -123,7 +124,20 @@ public static int getInt(final byte[] arr, final int offset) {
throw new BufferUnderflowException();
}
final int value = UNSAFE.getInt(arr, BYTE_ARRAY_BASE_OFFSET + offset);
- return NEED_CHANGE_BYTE_ORDER ? Integer.reverseBytes(value) : value;
+ return MACHINE_IS_LITTLE_ENDIAN ? Integer.reverseBytes(value) : value;
+ }
+
+ /**
+ * Reads an integer from the given array starting at the given offset. Array bytes are
+ * interpreted in NATIVE order.
+ *
+ * @param arr The byte array
+ * @param offset The offset to read an integer at
+ * @return The integer number
+ */
+ public static int getIntUnsafeLittleEndian(final byte[] arr, final long offset) {
+ final int value = UNSAFE.getInt(arr, BYTE_ARRAY_BASE_OFFSET + offset);
+ return MACHINE_IS_LITTLE_ENDIAN ? value : Integer.reverseBytes(value);
}
/**
@@ -139,8 +153,35 @@ public static long getLong(final byte[] arr, final int offset) {
if (arr.length < offset + Long.BYTES) {
throw new BufferUnderflowException();
}
+ return getLongNoChecks(arr, offset);
+ }
+
+ /**
+ * Reads a long from the given array starting at the given offset. Array bytes are
+ * interpreted in BIG_ENDIAN order.
+ *
+ * @param arr The byte array
+ * @param offset The offset to read a long at
+ * @return The long number
+ * @throws java.nio.BufferOverflowException If array length is less than offset + long bytes
+ */
+ public static long getLongNoChecks(final byte[] arr, final long offset) {
+ final long value = UNSAFE.getLong(arr, BYTE_ARRAY_BASE_OFFSET + offset);
+ return MACHINE_IS_LITTLE_ENDIAN ? Long.reverseBytes(value) : value;
+ }
+
+ /**
+ * Reads a long from the given array starting at the given offset. Array bytes are
+ * interpreted in LITTLE_ENDIAN order.
+ *
+ * @param arr The byte array
+ * @param offset The offset to read a long at
+ * @return The long number
+ * @throws java.nio.BufferOverflowException If array length is less than offset + long bytes
+ */
+ public static long getLongNoChecksLittleEndian(final byte[] arr, final long offset) {
final long value = UNSAFE.getLong(arr, BYTE_ARRAY_BASE_OFFSET + offset);
- return NEED_CHANGE_BYTE_ORDER ? Long.reverseBytes(value) : value;
+ return MACHINE_IS_LITTLE_ENDIAN ? value : Long.reverseBytes(value);
}
/**
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/BufferedData.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/BufferedData.java
index 43929d5b..f8cd2c9d 100644
--- a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/BufferedData.java
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/BufferedData.java
@@ -3,6 +3,7 @@
import static java.nio.ByteOrder.BIG_ENDIAN;
+import com.hedera.pbj.runtime.NonCryptographicHashing;
import com.hedera.pbj.runtime.io.ReadableSequentialData;
import com.hedera.pbj.runtime.io.WritableSequentialData;
import edu.umd.cs.findbugs.annotations.NonNull;
@@ -230,13 +231,13 @@ public boolean equals(final Object o) {
}
/**
- * Get hash based on contents of this buffer
+ * Get hash based on the contents of this buffer
*
* @return hash code
*/
@Override
public int hashCode() {
- return buffer.hashCode();
+ return (int) NonCryptographicHashing.hash64(buffer);
}
// ================================================================================================================
diff --git a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/Bytes.java b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/Bytes.java
index 59aee2c6..6307fac2 100644
--- a/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/Bytes.java
+++ b/pbj-core/pbj-runtime/src/main/java/com/hedera/pbj/runtime/io/buffer/Bytes.java
@@ -3,6 +3,7 @@
import static java.util.Objects.requireNonNull;
+import com.hedera.pbj.runtime.NonCryptographicHashing;
import com.hedera.pbj.runtime.io.DataEncodingException;
import com.hedera.pbj.runtime.io.ReadableSequentialData;
import com.hedera.pbj.runtime.io.UnsafeUtils;
@@ -111,7 +112,7 @@ private Bytes(@NonNull final byte[] data, final int offset, final int length) {
/**
* Create a new {@link Bytes} over the contents of the given byte array. This does not copy data it just
- * wraps so any changes to array's contents will be visible in the returned result.
+ * wraps, so any changes to array's contents will be visible in the returned result.
*
* @param byteArray The byte array to wrap
* @return new {@link Bytes} with same contents as byte array
@@ -192,9 +193,9 @@ public static Bytes merge(@NonNull final Bytes bytes1, @NonNull final Bytes byte
/**
* Returns the first byte offset of {@code needle} inside {@code haystack},
* or –1 if it is not present.
- *
- * Offsets are *relative to the start of the Bytes slice*, so 0 means
- * “starts exactly at haystack.start”.
+ *
+ * Offsets are relative to the start of the Bytes slice, so 0 means “starts exactly at haystack.start”.
+ *
*/
public static int indexOf(@NonNull final Bytes haystack, @NonNull final Bytes needle) {
requireNonNull(haystack);
@@ -537,11 +538,7 @@ public boolean equals(@Nullable final Object o) {
@Override
public int hashCode() {
if (hashCode == 0) {
- int h = 1;
- for (int i = start + length - 1; i >= start; i--) {
- h = 31 * h + UnsafeUtils.getArrayByteNoChecks(buffer, i);
- }
- hashCode = h;
+ hashCode = (int) NonCryptographicHashing.hash64(buffer, start, length);
}
return hashCode;
}
diff --git a/pbj-core/pbj-runtime/src/test/java/com/hedera/pbj/runtime/NonCryptographicHashTest.java b/pbj-core/pbj-runtime/src/test/java/com/hedera/pbj/runtime/NonCryptographicHashTest.java
new file mode 100644
index 00000000..3c5d2dcc
--- /dev/null
+++ b/pbj-core/pbj-runtime/src/test/java/com/hedera/pbj/runtime/NonCryptographicHashTest.java
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.runtime;
+
+import static com.hedera.pbj.runtime.NonCryptographicHashing.hash64;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.hedera.pbj.runtime.io.UnsafeUtils;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+@DisplayName("Non-Cryptographic Hash Test")
+class NonCryptographicHashTest {
+ /**
+ * Test the hash64(long) method with known values. The computation is very simple to do with any
+ * calculator,
+ */
+ @Test
+ @DisplayName("Test Hash64(long) Long with Known Values")
+ void testHash64Long() {
+ assertEquals(605873356528442819L, NonCryptographicHashing.hash64(0L));
+ assertEquals(4748194389872103055L, NonCryptographicHashing.hash64(1L));
+ assertEquals(5797980124308584942L, NonCryptographicHashing.hash64(-1L));
+ assertEquals(6218562537029544279L, NonCryptographicHashing.hash64(1234567890123456789L));
+ }
+
+ /**
+ * Test the hash64(byte[]) method with an empty byte array. This computation is also very simple
+ * to do with any calculator, and the result is known. We want to show that hashing an empty
+ * array is OK.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[]) Empty Array")
+ void testHash64ByteArrayEmpty() {
+ assertEquals(-6996494465910161660L, NonCryptographicHashing.hash64(new byte[0]));
+ }
+
+ /**
+ * Test the hash64(byte[], int, int) method with an empty byte array, position 0, and length 0.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[], int, int) Empty Array with Valid Position and Length")
+ void testHash64ByteArrayEmptyWithPositionAndLength() {
+ assertEquals(-6996494465910161660L, NonCryptographicHashing.hash64(new byte[0], 0, 0));
+ }
+
+ /**
+ * Test the hash64(byte[], int, int) method with position > length of the byte array.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[], int, int) Position Exceeds Array Length")
+ @Disabled("Disabled for now. I don't want to do the check and slow things down. Do we care about this?")
+ void testHash64ByteArrayPositionExceedsLength() {
+ byte[] arr = new byte[5];
+ // At the moment just returns the hash of 255.
+ assertThrows(IndexOutOfBoundsException.class, () -> NonCryptographicHashing.hash64(arr, 6, 0));
+ }
+
+ /**
+ * Test the hash64(byte[], int, int) method with position < 0.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[], int, int) Negative Position")
+ @Disabled("Disabled for now. I don't want to do the check and slow things down. Do we care about this?")
+ void testHash64ByteArrayNegativePosition() {
+ byte[] arr = new byte[5];
+ // At the moment just returns the hash of 255.
+ assertThrows(IndexOutOfBoundsException.class, () -> NonCryptographicHashing.hash64(arr, -1, 0));
+ }
+
+ /**
+ * Test the hash64(byte[], int, int) method with length < 0.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[], int, int) Negative Length")
+ @Disabled("Disabled for now. I don't want to do the check and slow things down. Do we care about this?")
+ void testHash64ByteArrayNegativeLength() {
+ byte[] arr = new byte[5];
+ // At the moment just returns the hash of 255.
+ assertThrows(IllegalArgumentException.class, () -> NonCryptographicHashing.hash64(arr, 0, -1));
+ }
+
+ /**
+ * Test the hash64(byte[], int, int) method with position + length > byte array length.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[], int, int) Position Plus Length Exceeds Array Length")
+ void testHash64ByteArrayPositionPlusLengthExceeds() {
+ byte[] arr = new byte[5];
+ assertThrows(IndexOutOfBoundsException.class, () -> NonCryptographicHashing.hash64(arr, 2, 4));
+ }
+
+ /**
+ * Test the hash64(byte[]) method with a one-byte array. This shows what happens if we have less than 8 bytes.
+ * The constant was found by calculating by hand the expected result.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[]) with array less than 8 bytes")
+ void testHash64ByteArrayLessThan8Bytes() {
+ byte[] arr = {(byte) 1};
+ assertEquals(1343923460066354394L, NonCryptographicHashing.hash64(arr));
+ }
+
+ /**
+ * Test the hash64(byte[]) method with an 8-byte array. This shows what happens if we test with exactly 8 bytes.
+ * The constant was found by calculating by hand the expected result.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[]) with 8 bytes")
+ void testHash64ByteArray8Bytes() {
+ byte[] arr = {(byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6, (byte) 7, (byte) 8};
+
+ assertEquals(-3104306485754735749L, NonCryptographicHashing.hash64(arr));
+ }
+
+ /**
+ * Test the hash64(byte[]) method with a 12-byte array. This shows what happens if we test with more than
+ * 8 bytes, but not a multiple of 8. The constant was found by calculating by hand the expected result.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[]) with larger non-multiple of 8 bytes")
+ void testHash64ByteArrayMoreThan8ButNotMultipleOf8Bytes() {
+ byte[] arr = {
+ (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6, (byte) 7, (byte) 8, (byte) 9, (byte) 10,
+ (byte) 11, (byte) 12
+ };
+
+ assertEquals(3639540625541984507L, NonCryptographicHashing.hash64(arr));
+ }
+
+ /**
+ * Test the hash64(byte[]) method with a 16-byte array. This shows what happens for arrays that are a multiple of 8.
+ * The constant was found by calculating by hand the expected result.
+ */
+ @Test
+ @DisplayName("Test Hash64(byte[]) with multiple of 8 bytes")
+ void testHash64ByteArrayMultipleOf8Bytes() {
+ byte[] arr = {
+ (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6, (byte) 7, (byte) 8, (byte) 9, (byte) 10,
+ (byte) 11, (byte) 12, (byte) 13, (byte) 14, (byte) 15, (byte) 16
+ };
+
+ assertEquals(7790396302089317864L, NonCryptographicHashing.hash64(arr));
+ }
+
+ /**
+ * While not comprehensive, this test provides a basic sanity check that if you are given two arrays of different
+ * lengths, but they both have the same high byte set and all other bytes are zero, then they generate different
+ * hashes.
+ */
+ @Test
+ @DisplayName("Test arrays of various lengths with high byte set and all else zero do not collide")
+ void testLeadingOneHasNoCollisions() {
+ Set hashes = new HashSet<>();
+ for (int len = 1; len <= 16; len++) {
+ byte[] leadingOne = new byte[len];
+ long h1 = NonCryptographicHashing.hash64(leadingOne);
+ assertTrue(hashes.add(h1)); // asserts each is unique
+ }
+ }
+
+ /**
+ * While not comprehensive, this test provides a basic sanity check that if you are given two arrays of different
+ * lengths, but they both have all bytes set to 1, then they generate different hashes.
+ */
+ @Test
+ void testAllOnesHasNoCollisions() {
+ Map collisions = new HashMap<>();
+ Set hashes = new HashSet<>();
+ for (int len = 1; len <= 16; len++) {
+ byte[] allOnes = new byte[len];
+ for (int i = 0; i < len; i++) allOnes[i] = (byte) 0xFF;
+ long h1 = NonCryptographicHashing.hash64(allOnes);
+ if (!collisions.containsKey(h1)) collisions.put(h1, len);
+ assertTrue(
+ hashes.add(h1),
+ "Found duplicate hash on iteration " + len + " collided with "
+ + collisions.get(h1)); // asserts each is unique
+ }
+ }
+
+ /**
+ * This test checks that the hash64 method does not produce collisions for small arrays.
+ * It verifies that all possible byte combinations for arrays of length 1 and 2 produce unique hashes.
+ */
+ @Test
+ @DisplayName("Test No Collisions for Small Arrays")
+ void testNoCollisionsSmallArrays() {
+ // Length 1: all 256
+ Set set1 = new HashSet<>();
+ for (int i = 0; i < 256; i++) {
+ byte[] ba = {(byte) i};
+ assertTrue(set1.add(NonCryptographicHashing.hash64(ba)));
+ }
+
+ // Length 2: all 65536
+ Set set2 = new HashSet<>();
+ for (int i = 0; i < 256; i++) {
+ for (int j = 0; j < 256; j++) {
+ byte[] ba = {(byte) i, (byte) j};
+ assertTrue(set2.add(NonCryptographicHashing.hash64(ba)));
+ }
+ }
+ }
+
+ /**
+ * This test checks that the hash64 method does not produce collisions for larger sets of data.
+ * It verifies that all possible byte combinations up to the number 100,000 produce unique hashes.
+ */
+ @Test
+ @DisplayName("Test No Collisions for Large Sets")
+ void testNoCollisionsLargeSet() {
+ final int num = 100_000;
+ Set set = new HashSet<>();
+ for (int i = 0; i < num; i++) {
+ byte[] ba = ByteBuffer.allocate(4).putInt(i).array();
+ assertTrue(set.add(NonCryptographicHashing.hash64(ba)));
+ }
+ }
+
+ @Test
+ @DisplayName("Test Collisions with non-random data")
+ void testLowCollisionsLargeSet() {
+ // Given an 8 byte array, try changing only the first 2 bytes, and see if we get collisions.
+ // A bad hash function would produce many collisions here. Then try again but changing out the middle
+ // 2 bytes. And do the same for the last 2 bytes.
+ final Set firstBytesSet = new HashSet<>();
+ final Set middleBytesSet = new HashSet<>();
+ final Set lastBytesSet = new HashSet<>();
+ final byte[] arr = {
+ (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
+ (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08
+ };
+ for (int i = 0; i < 256; i++) {
+ for (int j = 0; j < 256; j++) {
+ // Change the first two bytes
+ arr[6] = (byte) 0x07; // Reset last two bytes
+ arr[7] = (byte) 0x08; // Reset last two bytes
+ arr[0] = (byte) i;
+ arr[1] = (byte) j;
+ long hash1 = NonCryptographicHashing.hash64(arr);
+ assertTrue(
+ firstBytesSet.add(hash1),
+ "Collision found with first two bytes: iteration=" + i + ", long="
+ + Long.toHexString(UnsafeUtils.getLong(arr, 0)));
+
+ // Change the middle two bytes
+ arr[0] = (byte) 0x01; // Reset first two bytes
+ arr[1] = (byte) 0x02; // Reset first two bytes
+ arr[3] = (byte) i;
+ arr[4] = (byte) j;
+ long hash2 = NonCryptographicHashing.hash64(arr);
+ assertTrue(
+ middleBytesSet.add(hash2),
+ "Collision found with middle two bytes: iteration=" + i + ", long="
+ + Long.toHexString(UnsafeUtils.getLong(arr, 0)));
+
+ // Change the last two bytes
+ arr[3] = (byte) 0x03; // Reset middle two bytes
+ arr[4] = (byte) 0x04; // Reset middle two bytes
+ arr[6] = (byte) i;
+ arr[7] = (byte) j;
+ long hash3 = NonCryptographicHashing.hash64(arr);
+ assertTrue(
+ lastBytesSet.add(hash3),
+ "Collision found with last two bytes: iteration=" + i + ", long="
+ + Long.toHexString(UnsafeUtils.getLong(arr, 0)));
+ }
+ }
+ }
+
+ /**
+ * Checks that hashing a byte array with an offset produces the same result as hashing the same bytes directly.
+ */
+ @Test
+ @DisplayName("Test Hash with Offset")
+ void testHashWithOffset() {
+ byte[] large = new byte[255];
+ for (int i = 0; i < large.length; i++) {
+ large[i] = (byte) i;
+ }
+
+ // Try every subset where the start is changing but the length includes the last byte.
+ for (int i = 0; i < large.length; i++) {
+ int length = large.length - i;
+ byte[] subset = new byte[length];
+ System.arraycopy(large, i, subset, 0, length);
+ long expected = NonCryptographicHashing.hash64(subset);
+ long actual = NonCryptographicHashing.hash64(large, i, length);
+ assertEquals(expected, actual, "Hash with offset where start changes: " + i);
+ }
+
+ // Try every subset where the start is always 0 but the length is changing.
+ for (int i = 0; i < large.length; i++) {
+ int length = large.length - i;
+ byte[] subset = new byte[length];
+ System.arraycopy(large, 0, subset, 0, length);
+ long expected = NonCryptographicHashing.hash64(subset);
+ long actual = NonCryptographicHashing.hash64(large, 0, length);
+ assertEquals(expected, actual, "Hash with offset where length changes: " + i);
+ }
+ }
+
+ /**
+ * This test does not attempt to verify statistical properties of the hash functions.
+ * Its purpose is to ensure that none of the methods cause a crash.
+ */
+ @Test
+ @DisplayName("Test hash64")
+ void testHash64() {
+ final long seed = 842025;
+ final Random random = new Random(seed);
+
+ assertDoesNotThrow(() -> {
+ hash64(random.nextLong());
+
+ for (int i = 0; i < 100; i++) {
+ final byte[] bytes = new byte[i];
+ hash64(bytes);
+ }
+ });
+ }
+
+ @Test
+ @DisplayName("Hashes Are Not Degenerate 64")
+ void hashesAreNonDegenerate64() {
+ final long seed = 842025;
+ final Random random = new Random(seed);
+
+ assertNotEquals(0, hash64(0));
+ assertNotEquals(0, hash64(random.nextLong()));
+
+ for (int i = 0; i < 100; i++) {
+ final byte[] bytes = new byte[i];
+ assertNotEquals(0, hash64(bytes), "Hashes should be non-degenerate");
+ }
+ }
+}
diff --git a/pbj-integration-tests/build.gradle.kts b/pbj-integration-tests/build.gradle.kts
index 31913083..1b21018c 100644
--- a/pbj-integration-tests/build.gradle.kts
+++ b/pbj-integration-tests/build.gradle.kts
@@ -44,6 +44,8 @@ testModuleInfo {
requires("io.helidon.common")
requires("io.helidon.common.tls")
requires("io.helidon.webclient.api")
+ requires("org.lz4.java")
+ requires("hash4j")
runtimeOnly("io.helidon.webclient.http2")
requires("io.helidon.webserver")
runtimeOnly("io.grpc.netty")
@@ -52,6 +54,8 @@ testModuleInfo {
}
jmhModuleInfo {
+ requires("org.lz4.java")
+ requires("hash4j")
requires("com.hedera.pbj.runtime")
requires("com.google.protobuf.util")
}
@@ -64,12 +68,19 @@ configurations.testRuntimeClasspath {
}
// IMPROVE: Test code should not have a direct dependency to 'com.hedera.pbj.compiler'
-dependencies { testImplementation("com.hedera.pbj:pbj-compiler") { isTransitive = false } }
+dependencies {
+ testImplementation("com.hedera.pbj:pbj-compiler") { isTransitive = false }
+ implementation("org.lz4:lz4-java:1.8.0")
+ implementation("com.dynatrace.hash4j:hash4j:0.25.0")
+}
dependencyAnalysis { issues { all { onAny { exclude("com.hedera.pbj:pbj-compiler") } } } }
// IMPROVE: JMH code should not depend on test code
-jmh { includeTests = true }
+jmh {
+ includeTests = true
+ includes = listOf("com.hedera.pbj.integration.jmh.hashing.NonCryptographicHashingBench")
+}
// Avoid a clash with Google protoc models when .proto files don't specify `pbj.java_package`:
pbj { javaPackageSuffix = ".pbj.integration.tests" }
diff --git a/pbj-integration-tests/gradle/modules.properties b/pbj-integration-tests/gradle/modules.properties
new file mode 100644
index 00000000..460eae7d
--- /dev/null
+++ b/pbj-integration-tests/gradle/modules.properties
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: Apache-2.0
+
+# Jars that are not yet modules used in the integration tests.
+com.google.api.gax=com.google.api:gax
+org.lz4.java=org.lz4:lz4-java
+hash4j=com.dynatrace.hash4j:hash4j
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/CountingArray.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/CountingArray.java
new file mode 100644
index 00000000..ff7c5bcc
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/CountingArray.java
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.IntStream;
+
+/**
+ * An array that counts occurrences of indices in the range [0, 4,294,967,295]. It uses 4 byte arrays to store counts
+ * up to 250 and an overflow map for counts above 250.
+ */
+public final class CountingArray {
+ /** Maximum value for the index, 2^32 */
+ private static final long MAX_VALUE = 4_294_967_296L; // 2^32
+ /** 4x 1 GB arrays to split the integer space into 4 parts */
+ private final byte[][] counts = new byte[4][1_073_741_824];
+ /** Overflow map for counts above 250 */
+ private final Map overflowMap = new HashMap<>();
+
+ /**
+ * Clears all the counts
+ */
+ public void clear() {
+ for (byte[] subArray : counts) {
+ Arrays.fill(subArray, (byte) 0);
+ }
+ overflowMap.clear();
+ }
+
+ /**
+ * Returns the number of counts greater than zero across all indices.
+ * This includes counts in the overflow map.
+ *
+ * @return the number of counts greater than zero
+ */
+ public long numberOfGreaterThanZeroCounts() {
+ long count = Arrays.stream(counts)
+ .parallel()
+ .mapToLong(subArray ->
+ // Count values > 0 and <= 250 in each subArray
+ IntStream.range(0, subArray.length)
+ .map(i -> Byte.toUnsignedInt(subArray[i]))
+ .filter(unsignedValue -> unsignedValue > 0 && unsignedValue <= 250)
+ .count())
+ .sum();
+ return count
+ + overflowMap.values().stream().mapToLong(Integer::longValue).sum();
+ }
+
+ /**
+ * Returns the number of counts greater than zero across all indices.
+ * This includes counts in the overflow map.
+ *
+ * @return the number of counts greater than one
+ */
+ public long numberOfGreaterThanOneCounts() {
+ long count = Arrays.stream(counts)
+ .parallel()
+ .mapToLong(subArray ->
+ // Count values > 1 and <= 250 in each subArray
+ IntStream.range(0, subArray.length)
+ .map(i -> Byte.toUnsignedInt(subArray[i]))
+ .filter(unsignedValue -> unsignedValue > 1 && unsignedValue <= 250)
+ .count())
+ .sum();
+ return count
+ + overflowMap.values().stream().mapToLong(Integer::longValue).sum();
+ }
+
+ /**
+ * Returns the number of 0 counts across all indices.
+ *
+ * @return the number of zero counts
+ */
+ public long numberOfZeroCounts() {
+ long count = 0;
+ for (byte[] subArray : counts) {
+ for (byte b : subArray) {
+ if (b == 0) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Increments the count for the given index.
+ *
+ * @param index the index to increment, must be in the range [0, 4,294,967,295]
+ */
+ public void increment(long index) {
+ if (index < 0 || index >= MAX_VALUE) {
+ throw new IndexOutOfBoundsException("index: " + index);
+ }
+ int subArrayIndex = (int) (index >>> 30); // 2^30 = 1 GB
+ int indexInSubArray = (int) (index & 0x3FFFFFFF); // 2^30 - 1
+ byte[] subArray = counts[subArrayIndex];
+ int currentValueUnsigned = Byte.toUnsignedInt(subArray[indexInSubArray]);
+ if (currentValueUnsigned <= 250) {
+ // Increment the count in the sub-array using value as unsigned byte
+ final int newValueUnsigned = (currentValueUnsigned + 1) & 0xFF; // wrap at 255
+ subArray[indexInSubArray] = (byte) newValueUnsigned;
+ } else {
+ // Handle overflow
+ subArray[indexInSubArray] = Byte.MIN_VALUE; // marker for overflow
+ overflowMap.compute(index, (key, value) -> value == null ? 250 : value + 1);
+ }
+ }
+
+ /**
+ * Prints the statistics of the counts, including the number of occurrences for each value from 0 to 250,
+ * and the overflow counts.
+ */
+ public void printStats(final StringBuilder resultStr) {
+ // count up number of bytes with each value 0 to 250
+ long[] valueCounts = new long[251]; // 0 to 250
+ for (byte[] subArray : counts) {
+ for (byte b : subArray) {
+ int unsignedValue = Byte.toUnsignedInt(b);
+ if (unsignedValue <= 250) {
+ valueCounts[unsignedValue]++;
+ }
+ }
+ }
+ // print the counts
+ resultStr.append(" Counts:");
+ for (int i = 0; i <= 250; i++) {
+ long count = valueCounts[i];
+ if (count > 0) {
+ resultStr.append(String.format(" %d=%,d", i, count));
+ }
+ }
+ // print overflow map sorted by index
+ resultStr.append("\n Overflow counts: " + overflowMap.size());
+ // overflowMap.entrySet().stream()
+ // .sorted(Map.Entry.comparingByKey())
+ // .forEach(entry -> resultStr.append(String.format(" %d=%,d", entry.getKey(),
+ // entry.getValue())));
+ resultStr.append("\n");
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/HashFunction.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/HashFunction.java
new file mode 100644
index 00000000..3d543582
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/HashFunction.java
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+public interface HashFunction {
+ long applyAsLong(@NonNull final byte[] bytes, int start, int length);
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/LongBitSet.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/LongBitSet.java
new file mode 100644
index 00000000..3c242b46
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/LongBitSet.java
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.Arrays;
+
+/**
+ * A simple long bit set implementation that uses an array of longs to represent bits.
+ */
+public final class LongBitSet {
+ private static final int BITS_PER_LONG = 64;
+ private static final int SHIFT = 6; // log2(64)
+ private static final long MASK = 0x3FL; // 63
+
+ private final long[] bits;
+ private final long maxBits;
+
+ private static final VarHandle BITS_HANDLE;
+
+ static {
+ try {
+ BITS_HANDLE = MethodHandles.arrayElementVarHandle(long[].class);
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ public LongBitSet(long size) {
+ // Round up to next power of 2
+ long numLongs = size / BITS_PER_LONG;
+ this.bits = new long[(int) numLongs];
+ this.maxBits = size;
+ }
+
+ public void clear() {
+ Arrays.fill(bits, 0L);
+ }
+
+ public void setBit(long index) {
+ if (index < 0 || index >= maxBits) {
+ throw new IndexOutOfBoundsException("index: " + index);
+ }
+
+ int longIndex = (int) (index >>> SHIFT);
+ long bitMask = 1L << (index & MASK);
+
+ bits[longIndex] |= bitMask;
+ }
+
+ public void setBitThreadSafe(long index) {
+ if (index < 0 || index >= maxBits) {
+ throw new IndexOutOfBoundsException("index: " + index);
+ }
+
+ int longIndex = (int) (index >>> SHIFT);
+ long bitMask = 1L << (index & MASK);
+
+ long current;
+ do {
+ current = (long) BITS_HANDLE.getVolatile(bits, longIndex);
+ if ((current & bitMask) != 0) {
+ return; // Already set
+ }
+ } while (!BITS_HANDLE.compareAndSet(bits, longIndex, current, current | bitMask));
+ }
+
+ public long cardinality() {
+ long count = 0;
+ for (long value : bits) {
+ count += Long.bitCount(value);
+ }
+ return count;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/NonCryptographicHashingBench.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/NonCryptographicHashingBench.java
new file mode 100644
index 00000000..446099c7
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/NonCryptographicHashingBench.java
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing;
+
+import com.hedera.pbj.integration.jmh.hashing.functions.CityHash;
+import com.hedera.pbj.integration.jmh.hashing.functions.CityHashUnsafe;
+import com.hedera.pbj.integration.jmh.hashing.functions.CityHashVarHandle;
+import com.hedera.pbj.integration.jmh.hashing.functions.FarmHash;
+import com.hedera.pbj.integration.jmh.hashing.functions.Guava;
+import com.hedera.pbj.integration.jmh.hashing.functions.Hash4j;
+import com.hedera.pbj.integration.jmh.hashing.functions.HighwayHash;
+import com.hedera.pbj.integration.jmh.hashing.functions.JavaStyleHashing;
+import com.hedera.pbj.integration.jmh.hashing.functions.LeemonMurmur;
+import com.hedera.pbj.integration.jmh.hashing.functions.LuceneMurmur3;
+import com.hedera.pbj.integration.jmh.hashing.functions.Md5;
+import com.hedera.pbj.integration.jmh.hashing.functions.MetroHash64;
+import com.hedera.pbj.integration.jmh.hashing.functions.Murmur3Fast;
+import com.hedera.pbj.integration.jmh.hashing.functions.Murmur3OpenHFT;
+import com.hedera.pbj.integration.jmh.hashing.functions.MurmurHash3;
+import com.hedera.pbj.integration.jmh.hashing.functions.OlegHash;
+import com.hedera.pbj.integration.jmh.hashing.functions.RapidHash3;
+import com.hedera.pbj.integration.jmh.hashing.functions.Sha256;
+import com.hedera.pbj.integration.jmh.hashing.functions.XXH3OpenHFT;
+import com.hedera.pbj.integration.jmh.hashing.functions.XXH3OpenHFT2;
+import com.hedera.pbj.integration.jmh.hashing.functions.XxHash;
+import com.hedera.pbj.integration.jmh.hashing.functions.XxHashRichard;
+import com.hedera.pbj.integration.jmh.hashing.functions.Xxh3AiCPort;
+import com.hedera.pbj.integration.jmh.hashing.functions.Xxh3Lz4;
+import com.hedera.pbj.integration.jmh.hashing.functions.Xxh3ai;
+import com.hedera.pbj.runtime.NonCryptographicHashing;
+import com.hedera.pbj.runtime.hashing.XXH3_64;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OperationsPerInvocation;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+
+@SuppressWarnings("unused")
+@State(Scope.Benchmark)
+@Fork(1)
+@Warmup(iterations = 6, time = 2)
+@Measurement(iterations = 4, time = 2)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@BenchmarkMode(Mode.AverageTime)
+public class NonCryptographicHashingBench {
+ public static final int SAMPLES = 10_000;
+
+ public enum HashAlgorithm {
+ MURMUR_3_FAST(Murmur3Fast::hash64),
+ FARM_HASH(FarmHash::hash64),
+ METRO_HASH(MetroHash64::hash64),
+ MURMUR_OPENHFT(Murmur3OpenHFT::hash64),
+ LEEMON_MURMUR(LeemonMurmur::hash64),
+ GUAVA_FARM_HASH(Guava::farmHash),
+ XXH3_OHFT2(XXH3OpenHFT2::hash64),
+ HIGHWAY_HASH_GOOGLE(HighwayHash::hash64),
+ LEEMON_64(NonCryptographicHashing::hash64),
+ LEEMON_64_XOR_32(NonCryptographicHashing::hash64xor32),
+ LEEMON_64_UPPER_32(NonCryptographicHashing::hash64upper32),
+ CITY_HASH(CityHash::cityHash64),
+ CITY_HASH_UNSAFE(CityHashUnsafe::cityHash64),
+ CITY_HASH_VAR(CityHashVarHandle::cityHash64),
+ LEEMON_32(NonCryptographicHashing::hash32),
+ MURMUR_HASH_3_32(MurmurHash3::murmurhash3_x86_32),
+ OLEG_32(OlegHash::hash32),
+ OLEG_32_2(OlegHash::hash32_2),
+ OLEG_64(OlegHash::hash64),
+ JAVA_31(JavaStyleHashing::hash31),
+ JAVA_255(JavaStyleHashing::hash255),
+ JAVA_256(JavaStyleHashing::hash256),
+ JAVA_257(JavaStyleHashing::hash257),
+ XXHASH_32(XxHash::xxHashCode),
+ XXHASH_RICHARD(XxHashRichard::hash),
+ XXHASH_64(XxHash::xxHashCodeFast),
+ XXH3_AI(Xxh3ai::xxh3HashCode),
+ XXH3_OHFT(XXH3OpenHFT::hash64),
+ XXH3_AI_C_PORT(Xxh3AiCPort::xxh3_64bits),
+ RAPID_HASH_3(RapidHash3::hashBytesToLong),
+ SHA_256(Sha256::hash32),
+ MD5(Md5::hash32),
+ MURMUR_3_32_GUAVA(Guava::murmurhash3_x86_32),
+ SIP_24_GUAVA(Guava::sipHash24),
+ LUCENE_MURMUR3(LuceneMurmur3::murmurhash3_x86_32),
+ LUCENE_MURMUR3_128(LuceneMurmur3::murmurhash3_x64_128),
+ XXH64_LZ4_JAVA(Xxh3Lz4::xxh_64bits_java),
+ XXH64_LZ4_NATIVE(Xxh3Lz4::xxh_64bits_native),
+ FARM_HASH_NA_HASH4J(Hash4j::hash_farm_hash),
+ FARM_HASH_UO_HASH4J(Hash4j::hash_farm_hash_uo),
+ XXH3_64_HASH4J(Hash4j::hash_xxh3_64),
+ MURMUR3_HASH4J(Hash4j::hash_murmur_3_32),
+ XXH3_64_PBJ(XXH3_64::hash_xxh3_64),
+ ;
+
+ public final HashFunction function;
+
+ HashAlgorithm(HashFunction function) {
+ this.function = function;
+ }
+ }
+
+ @Param({"4", "8", "9", "12", "40", "60", "1000"})
+ public int dataSize;
+
+ @Param({
+ "MURMUR_3_FAST",
+ "FARM_HASH",
+ "METRO_HASH",
+ "MURMUR_OPENHFT",
+ "LEEMON_MURMUR",
+ "GUAVA_FARM_HASH",
+ "XXH3_OHFT2",
+ "HIGHWAY_HASH_GOOGLE",
+ "LEEMON_64",
+ "LEEMON_64_XOR_32",
+ "LEEMON_64_UPPER_32",
+ "CITY_HASH",
+ "CITY_HASH_UNSAFE",
+ "CITY_HASH_VAR",
+ "LEEMON_32",
+ "MURMUR_HASH_3_32",
+ "OLEG_32",
+ "OLEG_32_2",
+ "OLEG_64",
+ "JAVA_31",
+ "JAVA_255",
+ "JAVA_256",
+ "JAVA_257",
+ "XXHASH_32",
+ "XXHASH_RICHARD",
+ "XXHASH_64",
+ "XXH3_AI",
+ "XXH3_OHFT",
+ "RAPID_HASH_3",
+ "SHA_256",
+ "MD5",
+ "MURMUR_3_32_GUAVA",
+ "SIP_24_GUAVA",
+ "LUCENE_MURMUR3",
+ "LUCENE_MURMUR3_128",
+ "XXH3_AI_C_PORT",
+ "XXH64_LZ4_JAVA",
+ "XXH64_LZ4_NATIVE",
+ "FARM_HASH_NA_HASH4J",
+ "FARM_HASH_UO_HASH4J",
+ "XXH3_64_HASH4J",
+ "MURMUR3_HASH4J",
+ "XXH3_64_PBJ",
+ })
+ public HashAlgorithm hashAlgorithm;
+
+ private Random random;
+ private List sampleBytes;
+
+ @Setup(Level.Trial)
+ public void setup() {
+ random = new Random(6351384163846453326L);
+ sampleBytes = IntStream.range(0, SAMPLES)
+ .mapToObj(i -> {
+ final byte[] bytes = new byte[dataSize];
+ random.nextBytes(bytes);
+ return bytes;
+ })
+ .distinct()
+ .toList();
+ }
+
+ @Benchmark
+ @OperationsPerInvocation(SAMPLES)
+ public void testHashing(Blackhole blackhole) {
+ long sum = 0;
+ for (final byte[] bytes : sampleBytes) {
+ long hash = hashAlgorithm.function.applyAsLong(bytes, 0, dataSize);
+ sum += hash;
+ }
+ blackhole.consume(sum);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/XxhTest.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/XxhTest.java
new file mode 100644
index 00000000..1cdc800f
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/XxhTest.java
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing;
+
+import com.hedera.pbj.integration.jmh.hashing.functions.XXH3OpenHFT;
+import com.hedera.pbj.integration.jmh.hashing.functions.XXH3OpenHFT2;
+import com.hedera.pbj.integration.jmh.hashing.functions.Xxh3AiCPort;
+import com.hedera.pbj.integration.jmh.hashing.functions.Xxh3Lz4;
+import com.hedera.pbj.integration.jmh.hashing.functions.Xxh3ai;
+import com.hedera.pbj.integration.jmh.hashing.functions.XxhSumCommandLine;
+import com.hedera.pbj.runtime.hashing.XXH3_64;
+import java.util.HexFormat;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.IntStream;
+
+public class XxhTest {
+ public static void main3(String[] args) {
+ // test with a large random data set
+ Random random = new Random(18971947891479L);
+ final AtomicBoolean allMatch = new AtomicBoolean(true);
+ IntStream.range(0, 5_000)
+ .parallel()
+ .forEach(i -> {
+ byte[] randomData = new byte[1 + random.nextInt(50)];
+ // byte[] randomData = new byte[1 + random.nextInt(10)];
+ random.nextBytes(randomData);
+ long testCodeHashResult = XXH3_64.hash_xxh3_64(randomData, 0, randomData.length);
+ long referenceExpectedHash = XxhSumCommandLine.hashXxh3_64(randomData, 0, randomData.length);
+ if (testCodeHashResult != referenceExpectedHash) {
+ System.err.printf(
+ "Mismatch for random data %d: Input: %s, Expected xxhsum: %016x, Xxh3AiCPort: %016x %n",
+ i, HexFormat.of().formatHex(randomData), referenceExpectedHash, testCodeHashResult);
+ allMatch.set(false);
+ }
+ });
+ if (allMatch.get()) {
+ System.out.println("All random data hashes match!");
+ } else {
+ System.err.println("Some random data hashes did not match!");
+ }
+ }
+
+ public static void main(String[] args) {
+ // compare hashes with other implementations
+ byte[] data = "hello world".getBytes();
+ System.out.println("Input data: " + HexFormat.of().formatHex(data));
+ long hash64 = Xxh3AiCPort.xxh3_64bits(data, 0, data.length);
+ long hash64_lz4_java = Xxh3Lz4.xxh_64bits_java(data, 0, data.length);
+ long hash64_lz4_native = Xxh3Lz4.xxh_64bits_native(data, 0, data.length);
+ long hash64ai = Xxh3ai.xxh3HashCode(data, 0, data.length);
+ long hash64OpenHFT = XXH3OpenHFT.hash64(data, 0, data.length);
+ long hash64OpenHFT2 = XXH3OpenHFT2.hash64(data, 0, data.length);
+ long hashSumXxh_64 = XxhSumCommandLine.hashXxh_64(data, 0, data.length);
+ long hashSumXxh3_64 = XxhSumCommandLine.hashXxh3_64(data, 0, data.length);
+ long hashXxh3Pbj = XXH3_64.hash_xxh3_64(data, 0, data.length);
+ // print hashes in hex
+ System.out.printf("XXH3 64-bit hash: %016x%n", hash64);
+ System.out.printf("XXH3 64-bit hash (LZ4 Java): %016x%n", hash64_lz4_java);
+ System.out.printf("XXH3 64-bit hash (LZ4 Native): %016x%n", hash64_lz4_native);
+ System.out.printf("XXH3 64-bit ai hash: %016x%n", hash64ai);
+ System.out.printf("XXH3 OpenHFT 64-bit hash: %016x%n", hash64OpenHFT);
+ System.out.printf("XXH3 OpenHFT2 64-bit hash: %016x%n", hash64OpenHFT2);
+ System.out.printf("XXH3 xxhsum 64-bit hash: %016x%n", hashSumXxh_64);
+ System.out.printf("XXH3 xxhsum 64-bit hash (XXH3): %016x%n", hashSumXxh3_64);
+ System.out.printf("XXH3 PBJ 64-bit hash: %016x%n", hashXxh3Pbj);
+
+ // test with a large random data set
+ Random random = new Random(18971947891479L);
+ for (int i = 0; i < 10; i++) {
+ byte[] randomData = new byte[1 + random.nextInt(1023)];
+ random.nextBytes(randomData);
+ long hash64Random = Xxh3AiCPort.xxh3_64bits(randomData, 0, randomData.length);
+ long hash64aiRandom = Xxh3ai.xxh3HashCode(randomData, 0, randomData.length);
+ long hash64OpenHFTRandom = XXH3OpenHFT.hash64(randomData, 0, randomData.length);
+ long hash64OpenHFT2Random = XXH3OpenHFT2.hash64(randomData, 0, randomData.length);
+ long hashSumXxh_64Random = XxhSumCommandLine.hashXxh_64(randomData, 0, randomData.length);
+ long hashSumXxh3_64Random = XxhSumCommandLine.hashXxh3_64(randomData, 0, randomData.length);
+ System.out.printf(
+ "Random data %d: expected xxh64: %016x expected xxh3_64: %016x -- XXH3 64-bit: %016x, ai: %016x, OpenHFT: %016x, OpenHFT2: %016x%n",
+ i,
+ hashSumXxh_64Random,
+ hashSumXxh3_64Random,
+ hash64Random,
+ hash64aiRandom,
+ hash64OpenHFTRandom,
+ hash64OpenHFT2Random);
+ }
+ final AtomicBoolean allMatch = new AtomicBoolean(true);
+ IntStream.range(0, 100).parallel().forEach(i -> {
+ byte[] randomData = new byte[1 + random.nextInt(1023)];
+ random.nextBytes(randomData);
+ long hash64OpenHFT2Random = XXH3OpenHFT2.hash64(randomData, 0, randomData.length);
+ long hashSumXxh3_64Random = XxhSumCommandLine.hashXxh3_64(randomData, 0, randomData.length);
+ if (hash64OpenHFT2Random != hashSumXxh3_64Random) {
+ System.err.printf(
+ "Mismatch for random data %d: Input: %s, Expected xxhsum: %016x, OpenHFT2: %016x %n",
+ i, HexFormat.of().formatHex(randomData), hashSumXxh3_64Random, hash64OpenHFT2Random);
+ allMatch.set(false);
+ }
+ });
+ if (allMatch.get()) {
+ System.out.println("All random data hashes match!");
+ } else {
+ System.err.println("Some random data hashes did not match!");
+ }
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHash.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHash.java
new file mode 100644
index 00000000..508c6442
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHash.java
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+/**
+ * CityHash implementation in Java. CityHash is a family of hash functions developed by Google, designed to be fast and
+ * efficient for hashing strings and byte arrays. Based on Apache code from tamtam180 - kirscheless at gmail.com
+ *
+ * @see Original Java Port Source
+ * @see Blog on CityHash
+ * @see CityHash Original Code
+ */
+public class CityHash {
+ private static final long k0 = 0xc3a5c85c97cb3127L;
+ private static final long k1 = 0xb492b66fbe98f273L;
+ private static final long k2 = 0x9ae16a3b2f90404fL;
+ private static final long k3 = 0xc949d7c7509e6557L;
+
+ private static long fetch64(byte[] s, int pos) {
+ return (((long) s[pos + 7] << 56)
+ + ((long) (s[pos + 6] & 255) << 48)
+ + ((long) (s[pos + 5] & 255) << 40)
+ + ((long) (s[pos + 4] & 255) << 32)
+ + ((long) (s[pos + 3] & 255) << 24)
+ + ((s[pos + 2] & 255) << 16)
+ + ((s[pos + 1] & 255) << 8)
+ + ((s[pos + 0] & 255) << 0));
+ }
+
+ private static int fetch32(byte[] s, int pos) {
+ return (((s[pos + 3] & 255) << 24) + ((s[pos + 2] & 255) << 16) + ((s[pos + 1] & 255) << 8) + ((s[pos] & 255)));
+ }
+
+ private static long rotate(long val, int shift) {
+ return shift == 0 ? val : (val >>> shift) | (val << (64 - shift));
+ }
+
+ private static long rotateByAtLeast1(long val, int shift) {
+ return (val >>> shift) | (val << (64 - shift));
+ }
+
+ private static long shiftMix(long val) {
+ return val ^ (val >>> 47);
+ }
+
+ private static final long kMul = 0x9ddfea08eb382d69L;
+
+ private static long hash128to64(long u, long v) {
+ long a = (u ^ v) * kMul;
+ a ^= (a >>> 47);
+ long b = (v ^ a) * kMul;
+ b ^= (b >>> 47);
+ b *= kMul;
+ return b;
+ }
+
+ private static long hashLen16(long u, long v) {
+ return hash128to64(u, v);
+ }
+
+ private static long hashLen0to16(byte[] s, int pos, int len) {
+ if (len > 8) {
+ long a = fetch64(s, pos);
+ long b = fetch64(s, pos + len - 8);
+ return hashLen16(a, rotateByAtLeast1(b + len, len)) ^ b;
+ }
+ if (len >= 4) {
+ long a = 0xffffffffL & fetch32(s, pos);
+ return hashLen16((a << 3) + len, 0xffffffffL & fetch32(s, pos + len - 4));
+ }
+ if (len > 0) {
+ int a = s[pos] & 0xFF;
+ int b = s[pos + (len >>> 1)] & 0xFF;
+ int c = s[pos + len - 1] & 0xFF;
+ int y = a + (b << 8);
+ int z = len + (c << 2);
+ return shiftMix(y * k2 ^ z * k3) * k2;
+ }
+ return k2;
+ }
+
+ private static long hashLen17to32(byte[] s, int pos, int len) {
+ long a = fetch64(s, pos + 0) * k1;
+ long b = fetch64(s, pos + 8);
+ long c = fetch64(s, pos + len - 8) * k2;
+ long d = fetch64(s, pos + len - 16) * k0;
+ return hashLen16(rotate(a - b, 43) + rotate(c, 30) + d, a + rotate(b ^ k3, 20) - c + len);
+ }
+
+ private static long[] weakHashLen32WithSeeds(long w, long x, long y, long z, long a, long b) {
+ a += w;
+ b = rotate(b + a + z, 21);
+ long c = a;
+ a += x;
+ a += y;
+ b += rotate(a, 44);
+ return new long[] {a + z, b + c};
+ }
+
+ private static long[] weakHashLen32WithSeeds(byte[] s, int pos, long a, long b) {
+ return weakHashLen32WithSeeds(
+ fetch64(s, pos + 0), fetch64(s, pos + 8), fetch64(s, pos + 16), fetch64(s, pos + 24), a, b);
+ }
+
+ private static long hashLen33to64(byte[] s, int pos, int len) {
+
+ long z = fetch64(s, pos + 24);
+ long a = fetch64(s, pos + 0) + (fetch64(s, pos + len - 16) + len) * k0;
+ long b = rotate(a + z, 52);
+ long c = rotate(a, 37);
+
+ a += fetch64(s, pos + 8);
+ c += rotate(a, 7);
+ a += fetch64(s, pos + 16);
+
+ long vf = a + z;
+ long vs = b + rotate(a, 31) + c;
+
+ a = fetch64(s, pos + 16) + fetch64(s, pos + len - 32);
+ z = fetch64(s, pos + len - 8);
+ b = rotate(a + z, 52);
+ c = rotate(a, 37);
+ a += fetch64(s, pos + len - 24);
+ c += rotate(a, 7);
+ a += fetch64(s, pos + len - 16);
+
+ long wf = a + z;
+ long ws = b + rotate(a, 31) + c;
+ long r = shiftMix((vf + ws) * k2 + (wf + vs) * k0);
+
+ return shiftMix(r * k0 + vs) * k2;
+ }
+
+ public static long cityHash64(byte[] s, int pos, int len) {
+ if (len <= 32) {
+ if (len <= 16) {
+ return hashLen0to16(s, pos, len);
+ } else {
+ return hashLen17to32(s, pos, len);
+ }
+ } else if (len <= 64) {
+ return hashLen33to64(s, pos, len);
+ }
+
+ long x = fetch64(s, pos + len - 40);
+ long y = fetch64(s, pos + len - 16) + fetch64(s, pos + len - 56);
+ long z = hashLen16(fetch64(s, pos + len - 48) + len, fetch64(s, pos + len - 24));
+
+ long[] v = weakHashLen32WithSeeds(s, pos + len - 64, len, z);
+ long[] w = weakHashLen32WithSeeds(s, pos + len - 32, y + k1, x);
+ x = x * k1 + fetch64(s, pos + 0);
+
+ len = (len - 1) & (~63);
+ do {
+ x = rotate(x + y + v[0] + fetch64(s, pos + 8), 37) * k1;
+ y = rotate(y + v[1] + fetch64(s, pos + 48), 42) * k1;
+ x ^= w[1];
+ y += v[0] + fetch64(s, pos + 40);
+ z = rotate(z + w[0], 33) * k1;
+ v = weakHashLen32WithSeeds(s, pos + 0, v[1] * k1, x + w[0]);
+ w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], y + fetch64(s, pos + 16));
+ {
+ long swap = z;
+ z = x;
+ x = swap;
+ }
+ pos += 64;
+ len -= 64;
+ } while (len != 0);
+ return hashLen16(hashLen16(v[0], w[0]) + shiftMix(y) * k1 + z, hashLen16(v[1], w[1]) + x);
+ }
+ public static void main(String[] args) {
+ int x = 0;
+ for(int i = 0; i < 100; i++) {
+ int pairCount = i/2;
+ int pairCount2 = x++ >> 1;
+ System.out.println(i+" -> pairCount = " + pairCount+ ", pairCount2 = " + pairCount2+" (($xx_fieldCount & 1) == 0)="+((i & 1) == 0));
+ }
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHashUnsafe.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHashUnsafe.java
new file mode 100644
index 00000000..db102ec4
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHashUnsafe.java
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import com.hedera.pbj.runtime.io.UnsafeUtils;
+
+/**
+ * CityHash implementation in Java. CityHash is a family of hash functions developed by Google, designed to be fast and
+ * efficient for hashing strings and byte arrays. Based on Apache code from tamtam180 - kirscheless at gmail.com
+ *
+ * @see Original Java Port Source
+ * @see Blog on CityHash
+ * @see CityHash Original Code
+ */
+public class CityHashUnsafe {
+ private static final long k0 = 0xc3a5c85c97cb3127L;
+ private static final long k1 = 0xb492b66fbe98f273L;
+ private static final long k2 = 0x9ae16a3b2f90404fL;
+ private static final long k3 = 0xc949d7c7509e6557L;
+
+ private static long rotate(long val, int shift) {
+ return shift == 0 ? val : (val >>> shift) | (val << (64 - shift));
+ }
+
+ private static long rotateByAtLeast1(long val, int shift) {
+ return (val >>> shift) | (val << (64 - shift));
+ }
+
+ private static long shiftMix(long val) {
+ return val ^ (val >>> 47);
+ }
+
+ private static final long kMul = 0x9ddfea08eb382d69L;
+
+ private static long hash128to64(long u, long v) {
+ long a = (u ^ v) * kMul;
+ a ^= (a >>> 47);
+ long b = (v ^ a) * kMul;
+ b ^= (b >>> 47);
+ b *= kMul;
+ return b;
+ }
+
+ private static long hashLen16(long u, long v) {
+ return hash128to64(u, v);
+ }
+
+ private static long hashLen0to16(byte[] s, int pos, int len) {
+ if (len > 8) {
+ long a = UnsafeUtils.getLongNoChecksLittleEndian(s, pos);
+ long b = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 8);
+ return hashLen16(a, rotateByAtLeast1(b + len, len)) ^ b;
+ }
+ if (len >= 4) {
+ long a = 0xffffffffL & UnsafeUtils.getIntUnsafeLittleEndian(s, pos);
+ return hashLen16((a << 3) + len, 0xffffffffL & UnsafeUtils.getIntUnsafeLittleEndian(s, pos + len - 4));
+ }
+ if (len > 0) {
+ int a = s[pos] & 0xFF;
+ int b = s[pos + (len >>> 1)] & 0xFF;
+ int c = s[pos + len - 1] & 0xFF;
+ int y = a + (b << 8);
+ int z = len + (c << 2);
+ return shiftMix(y * k2 ^ z * k3) * k2;
+ }
+ return k2;
+ }
+
+ private static long hashLen17to32(byte[] s, int pos, int len) {
+ long a = UnsafeUtils.getLongNoChecksLittleEndian(s, pos) * k1;
+ long b = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 8);
+ long c = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 8) * k2;
+ long d = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 16) * k0;
+ return hashLen16(rotate(a - b, 43) + rotate(c, 30) + d, a + rotate(b ^ k3, 20) - c + len);
+ }
+
+ private static long[] weakHashLen32WithSeeds(long w, long x, long y, long z, long a, long b) {
+ a += w;
+ b = rotate(b + a + z, 21);
+ long c = a;
+ a += x;
+ a += y;
+ b += rotate(a, 44);
+ return new long[] {a + z, b + c};
+ }
+
+ private static long[] weakHashLen32WithSeeds(byte[] s, int pos, long a, long b) {
+ return weakHashLen32WithSeeds(
+ UnsafeUtils.getLongNoChecksLittleEndian(s, pos),
+ UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 8),
+ UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 16),
+ UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 24),
+ a,
+ b);
+ }
+
+ private static long hashLen33to64(byte[] s, int pos, int len) {
+
+ long z = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 24);
+ long a = UnsafeUtils.getLongNoChecksLittleEndian(s, pos)
+ + (UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 16) + len) * k0;
+ long b = rotate(a + z, 52);
+ long c = rotate(a, 37);
+
+ a += UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 8);
+ c += rotate(a, 7);
+ a += UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 16);
+
+ long vf = a + z;
+ long vs = b + rotate(a, 31) + c;
+
+ a = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 16)
+ + UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 32);
+ z = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 8);
+ b = rotate(a + z, 52);
+ c = rotate(a, 37);
+ a += UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 24);
+ c += rotate(a, 7);
+ a += UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 16);
+
+ long wf = a + z;
+ long ws = b + rotate(a, 31) + c;
+ long r = shiftMix((vf + ws) * k2 + (wf + vs) * k0);
+
+ return shiftMix(r * k0 + vs) * k2;
+ }
+
+ public static long cityHash64(byte[] s, int pos, int len) {
+ if (len <= 32) {
+ if (len <= 16) {
+ return hashLen0to16(s, pos, len);
+ } else {
+ return hashLen17to32(s, pos, len);
+ }
+ } else if (len <= 64) {
+ return hashLen33to64(s, pos, len);
+ }
+
+ long x = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 40);
+ long y = UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 16)
+ + UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 56);
+ long z = hashLen16(
+ UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 48) + len,
+ UnsafeUtils.getLongNoChecksLittleEndian(s, pos + len - 24));
+
+ long[] v = weakHashLen32WithSeeds(s, pos + len - 64, len, z);
+ long[] w = weakHashLen32WithSeeds(s, pos + len - 32, y + k1, x);
+ x = x * k1 + UnsafeUtils.getLongNoChecksLittleEndian(s, pos);
+
+ len = (len - 1) & (~63);
+ do {
+ x = rotate(x + y + v[0] + UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 8), 37) * k1;
+ y = rotate(y + v[1] + UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 48), 42) * k1;
+ x ^= w[1];
+ y += v[0] + UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 40);
+ z = rotate(z + w[0], 33) * k1;
+ v = weakHashLen32WithSeeds(s, pos, v[1] * k1, x + w[0]);
+ w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], y + UnsafeUtils.getLongNoChecksLittleEndian(s, pos + 16));
+ {
+ long swap = z;
+ z = x;
+ x = swap;
+ }
+ pos += 64;
+ len -= 64;
+ } while (len != 0);
+ return hashLen16(hashLen16(v[0], w[0]) + shiftMix(y) * k1 + z, hashLen16(v[1], w[1]) + x);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHashVarHandle.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHashVarHandle.java
new file mode 100644
index 00000000..91a204f0
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/CityHashVarHandle.java
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * CityHash implementation in Java. CityHash is a family of hash functions developed by Google, designed to be fast and
+ * efficient for hashing strings and byte arrays. Based on Apache code from tamtam180 - kirscheless at gmail.com
+ *
+ * @see Original Java Port Source
+ * @see Blog on CityHash
+ * @see CityHash Original Code
+ */
+public class CityHashVarHandle {
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle INT_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final long k0 = 0xc3a5c85c97cb3127L;
+ private static final long k1 = 0xb492b66fbe98f273L;
+ private static final long k2 = 0x9ae16a3b2f90404fL;
+ private static final long k3 = 0xc949d7c7509e6557L;
+
+ private static long rotate(long val, int shift) {
+ return shift == 0 ? val : (val >>> shift) | (val << (64 - shift));
+ }
+
+ private static long rotateByAtLeast1(long val, int shift) {
+ return (val >>> shift) | (val << (64 - shift));
+ }
+
+ private static long shiftMix(long val) {
+ return val ^ (val >>> 47);
+ }
+
+ private static final long kMul = 0x9ddfea08eb382d69L;
+
+ private static long hash128to64(long u, long v) {
+ long a = (u ^ v) * kMul;
+ a ^= (a >>> 47);
+ long b = (v ^ a) * kMul;
+ b ^= (b >>> 47);
+ b *= kMul;
+ return b;
+ }
+
+ private static long hashLen16(long u, long v) {
+ return hash128to64(u, v);
+ }
+
+ private static long hashLen0to16(byte[] s, int pos, int len) {
+ if (len > 8) {
+ long a = (long) LONG_HANDLE.get(s, pos + 0);
+ long b = (long) LONG_HANDLE.get(s, pos + len - 8);
+ return hashLen16(a, rotateByAtLeast1(b + len, len)) ^ b;
+ }
+ if (len >= 4) {
+ long a = 0xffffffffL & (int) INT_HANDLE.get(s, pos);
+ return hashLen16((a << 3) + len, 0xffffffffL & (int) INT_HANDLE.get(s, pos + len - 4));
+ }
+ if (len > 0) {
+ int a = s[pos] & 0xFF;
+ int b = s[pos + (len >>> 1)] & 0xFF;
+ int c = s[pos + len - 1] & 0xFF;
+ int y = a + (b << 8);
+ int z = len + (c << 2);
+ return shiftMix(y * k2 ^ z * k3) * k2;
+ }
+ return k2;
+ }
+
+ private static long hashLen17to32(byte[] s, int pos, int len) {
+ long a = (long) LONG_HANDLE.get(s, pos) * k1;
+ long b = (long) LONG_HANDLE.get(s, pos + 8);
+ long c = (long) LONG_HANDLE.get(s, pos + len - 8) * k2;
+ long d = (long) LONG_HANDLE.get(s, pos + len - 16) * k0;
+ return hashLen16(rotate(a - b, 43) + rotate(c, 30) + d, a + rotate(b ^ k3, 20) - c + len);
+ }
+
+ private static long[] weakHashLen32WithSeeds(long w, long x, long y, long z, long a, long b) {
+ a += w;
+ b = rotate(b + a + z, 21);
+ long c = a;
+ a += x;
+ a += y;
+ b += rotate(a, 44);
+ return new long[] {a + z, b + c};
+ }
+
+ private static long[] weakHashLen32WithSeeds(byte[] s, int pos, long a, long b) {
+ return weakHashLen32WithSeeds(
+ (long) LONG_HANDLE.get(s, pos),
+ (long) LONG_HANDLE.get(s, pos + 8),
+ (long) LONG_HANDLE.get(s, pos + 16),
+ (long) LONG_HANDLE.get(s, pos + 24),
+ a,
+ b);
+ }
+
+ private static long hashLen33to64(byte[] s, int pos, int len) {
+ long z = (long) LONG_HANDLE.get(s, pos + 24);
+ long a = (long) LONG_HANDLE.get(s, pos) + ((long) LONG_HANDLE.get(s, pos + len - 16) + len) * k0;
+ long b = rotate(a + z, 52);
+ long c = rotate(a, 37);
+
+ a += (long) LONG_HANDLE.get(s, pos + 8);
+ c += rotate(a, 7);
+ a += (long) LONG_HANDLE.get(s, pos + 16);
+
+ long vf = a + z;
+ long vs = b + rotate(a, 31) + c;
+
+ a = (long) LONG_HANDLE.get(s, pos + 16) + (long) LONG_HANDLE.get(s, pos + len - 32);
+ z = (long) LONG_HANDLE.get(s, pos + len - 8);
+ b = rotate(a + z, 52);
+ c = rotate(a, 37);
+ a += (long) LONG_HANDLE.get(s, pos + len - 24);
+ c += rotate(a, 7);
+ a += (long) LONG_HANDLE.get(s, pos + len - 16);
+
+ long wf = a + z;
+ long ws = b + rotate(a, 31) + c;
+ long r = shiftMix((vf + ws) * k2 + (wf + vs) * k0);
+
+ return shiftMix(r * k0 + vs) * k2;
+ }
+
+ public static long cityHash64(byte[] s, int pos, int len) {
+ if (len <= 32) {
+ if (len <= 16) {
+ return hashLen0to16(s, pos, len);
+ } else {
+ return hashLen17to32(s, pos, len);
+ }
+ } else if (len <= 64) {
+ return hashLen33to64(s, pos, len);
+ }
+
+ long x = (long) LONG_HANDLE.get(s, pos + len - 40);
+ long y = (long) LONG_HANDLE.get(s, pos + len - 16) + (long) LONG_HANDLE.get(s, pos + len - 56);
+ long z = hashLen16((long) LONG_HANDLE.get(s, pos + len - 48) + len, (long) LONG_HANDLE.get(s, pos + len - 24));
+
+ long[] v = weakHashLen32WithSeeds(s, pos + len - 64, len, z);
+ long[] w = weakHashLen32WithSeeds(s, pos + len - 32, y + k1, x);
+ x = x * k1 + (long) LONG_HANDLE.get(s, pos);
+
+ len = (len - 1) & (~63);
+ do {
+ x = rotate(x + y + v[0] + (long) LONG_HANDLE.get(s, pos + 8), 37) * k1;
+ y = rotate(y + v[1] + (long) LONG_HANDLE.get(s, pos + 48), 42) * k1;
+ x ^= w[1];
+ y += v[0] + (long) LONG_HANDLE.get(s, pos + 40);
+ z = rotate(z + w[0], 33) * k1;
+ v = weakHashLen32WithSeeds(s, pos, v[1] * k1, x + w[0]);
+ w = weakHashLen32WithSeeds(s, pos + 32, z + w[1], y + (long) LONG_HANDLE.get(s, pos + 16));
+ {
+ long swap = z;
+ z = x;
+ x = swap;
+ }
+ pos += 64;
+ len -= 64;
+ } while (len != 0);
+ return hashLen16(hashLen16(v[0], w[0]) + shiftMix(y) * k1 + z, hashLen16(v[1], w[1]) + x);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/FarmHash.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/FarmHash.java
new file mode 100644
index 00000000..532d8eec
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/FarmHash.java
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import static java.lang.Long.rotateRight;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * Port of Google Guava FarmHash with no dependencies and using VarHandle. Also object allocation is avoided.
+ */
+@SuppressWarnings("DuplicatedCode")
+public final class FarmHash {
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ // Some primes between 2^63 and 2^64 for various uses.
+ private static final long K0 = 0xc3a5c85c97cb3127L;
+ private static final long K1 = 0xb492b66fbe98f273L;
+ private static final long K2 = 0x9ae16a3b2f90404fL;
+
+ public static long hash64(byte[] bytes, int offset, int length) {
+ if (length <= 16) {
+ return hashLength0to16(bytes, offset, length);
+ } else if (length <= 32) {
+ return hashLength17to32(bytes, offset, length);
+ } else if (length <= 64) {
+ return hashLength33To64(bytes, offset, length);
+ } else {
+ return hashLength65Plus(bytes, offset, length);
+ }
+ }
+
+ private static long shiftMix(long val) {
+ return val ^ (val >>> 47);
+ }
+
+ private static long hashLength16(long u, long v, long mul) {
+ long a = (u ^ v) * mul;
+ a ^= (a >>> 47);
+ long b = (v ^ a) * mul;
+ b ^= (b >>> 47);
+ b *= mul;
+ return b;
+ }
+
+ /**
+ * Computes intermediate hash of 32 bytes of byte array from the given offset. Results are
+ * returned in the output array because when we last measured, this was 12% faster than allocating
+ * new arrays every time.
+ */
+ private static void weakHashLength32WithSeeds(byte[] bytes, int offset, long seedA, long seedB, long[] output) {
+ long part1 = load64(bytes, offset);
+ long part2 = load64(bytes, offset + 8);
+ long part3 = load64(bytes, offset + 16);
+ long part4 = load64(bytes, offset + 24);
+
+ seedA += part1;
+ seedB = rotateRight(seedB + seedA + part4, 21);
+ long c = seedA;
+ seedA += part2;
+ seedA += part3;
+ seedB += rotateRight(seedA, 44);
+ output[0] = seedA + part4;
+ output[1] = seedB + c;
+ }
+
+ private static long hashLength0to16(byte[] bytes, int offset, int length) {
+ if (length >= 8) {
+ long mul = K2 + length * 2L;
+ long a = load64(bytes, offset) + K2;
+ long b = load64(bytes, offset + length - 8);
+ long c = rotateRight(b, 37) * mul + a;
+ long d = (rotateRight(a, 25) + b) * mul;
+ return hashLength16(c, d, mul);
+ }
+ if (length >= 4) {
+ long mul = K2 + length * 2;
+ long a = load32(bytes, offset) & 0xFFFFFFFFL;
+ return hashLength16(length + (a << 3), load32(bytes, offset + length - 4) & 0xFFFFFFFFL, mul);
+ }
+ if (length > 0) {
+ byte a = bytes[offset];
+ byte b = bytes[offset + (length >> 1)];
+ byte c = bytes[offset + (length - 1)];
+ int y = (a & 0xFF) + ((b & 0xFF) << 8);
+ int z = length + ((c & 0xFF) << 2);
+ return shiftMix(y * K2 ^ z * K0) * K2;
+ }
+ return K2;
+ }
+
+ private static long hashLength17to32(byte[] bytes, int offset, int length) {
+ long mul = K2 + length * 2L;
+ long a = load64(bytes, offset) * K1;
+ long b = load64(bytes, offset + 8);
+ long c = load64(bytes, offset + length - 8) * mul;
+ long d = load64(bytes, offset + length - 16) * K2;
+ return hashLength16(rotateRight(a + b, 43) + rotateRight(c, 30) + d, a + rotateRight(b + K2, 18) + c, mul);
+ }
+
+ private static long hashLength33To64(byte[] bytes, int offset, int length) {
+ long mul = K2 + length * 2L;
+ long a = load64(bytes, offset) * K2;
+ long b = load64(bytes, offset + 8);
+ long c = load64(bytes, offset + length - 8) * mul;
+ long d = load64(bytes, offset + length - 16) * K2;
+ long y = rotateRight(a + b, 43) + rotateRight(c, 30) + d;
+ long z = hashLength16(y, a + rotateRight(b + K2, 18) + c, mul);
+ long e = load64(bytes, offset + 16) * mul;
+ long f = load64(bytes, offset + 24);
+ long g = (y + load64(bytes, offset + length - 32)) * mul;
+ long h = (z + load64(bytes, offset + length - 24)) * mul;
+ return hashLength16(rotateRight(e + f, 43) + rotateRight(g, 30) + h, e + rotateRight(f + a, 18) + g, mul);
+ }
+
+ /*
+ * Compute an 8-byte hash of a byte array of length greater than 64 bytes.
+ */
+ private static long hashLength65Plus(byte[] bytes, int offset, int length) {
+ int seed = 81;
+ // For strings over 64 bytes we loop. Internal state consists of 56 bytes: v, w, x, y, and z.
+ long x = seed;
+ @SuppressWarnings("ConstantOverflow")
+ long y = seed * K1 + 113;
+ long z = shiftMix(y * K2 + 113) * K2;
+ long[] v = new long[2];
+ long[] w = new long[2];
+ x = x * K2 + load64(bytes, offset);
+
+ // Set end so that after the loop we have 1 to 64 bytes left to process.
+ int end = offset + ((length - 1) / 64) * 64;
+ int last64offset = end + ((length - 1) & 63) - 63;
+ do {
+ x = rotateRight(x + y + v[0] + load64(bytes, offset + 8), 37) * K1;
+ y = rotateRight(y + v[1] + load64(bytes, offset + 48), 42) * K1;
+ x ^= w[1];
+ y += v[0] + load64(bytes, offset + 40);
+ z = rotateRight(z + w[0], 33) * K1;
+ weakHashLength32WithSeeds(bytes, offset, v[1] * K1, x + w[0], v);
+ weakHashLength32WithSeeds(bytes, offset + 32, z + w[1], y + load64(bytes, offset + 16), w);
+ long tmp = x;
+ x = z;
+ z = tmp;
+ offset += 64;
+ } while (offset != end);
+ long mul = K1 + ((z & 0xFF) << 1);
+ // Operate on the last 64 bytes of input.
+ offset = last64offset;
+ w[0] += ((length - 1) & 63);
+ v[0] += w[0];
+ w[0] += v[0];
+ x = rotateRight(x + y + v[0] + load64(bytes, offset + 8), 37) * mul;
+ y = rotateRight(y + v[1] + load64(bytes, offset + 48), 42) * mul;
+ x ^= w[1] * 9;
+ y += v[0] * 9 + load64(bytes, offset + 40);
+ z = rotateRight(z + w[0], 33) * mul;
+ weakHashLength32WithSeeds(bytes, offset, v[1] * mul, x + w[0], v);
+ weakHashLength32WithSeeds(bytes, offset + 32, z + w[1], y + load64(bytes, offset + 16), w);
+ return hashLength16(
+ hashLength16(v[0], w[0], mul) + shiftMix(y) * K0 + x, hashLength16(v[1], w[1], mul) + z, mul);
+ }
+
+ /**
+ * Reads a 64 bit long in little-endian order from the given byte array at the specified offset.
+ * *
+ * @param input the object to access
+ * @param offset offset to the first byte to read within the byte sequence represented
+ * @return a 64 bit long value, little-endian encoded
+ */
+ private static long load64(final byte[] input, final int offset) {
+ return (long) LONG_HANDLE.get(input, offset);
+ }
+
+ /**
+ * Load 4 bytes from the provided array at the indicated offset.
+ *
+ * @param source the input bytes
+ * @param offset the offset into the array at which to start
+ * @return the value found in the array in the form of a long
+ */
+ private static int load32(byte[] source, int offset) {
+ // This is faster than using VarHandle for 4 bytes
+ return (source[offset] & 0xFF)
+ | ((source[offset + 1] & 0xFF) << 8)
+ | ((source[offset + 2] & 0xFF) << 16)
+ | ((source[offset + 3] & 0xFF) << 24);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Guava.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Guava.java
new file mode 100644
index 00000000..48dfae8a
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Guava.java
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import com.google.common.hash.Hashing;
+
+/**
+ * Guava hashing functions using the Guava library. So they can be easily used in JMH benchmarks or other tests.
+ *
+ * This class provides methods to compute MurmurHash3 and SipHash24 hashes using Guava's Hashing utilities.
+ *
+ */
+public final class Guava {
+
+ public static int murmurhash3_x86_32(byte[] data, int offset, int len) {
+ return Hashing.murmur3_32_fixed().hashBytes(data, offset, len).asInt();
+ }
+
+ public static int sipHash24(byte[] data, int offset, int len) {
+ return Hashing.sipHash24().hashBytes(data, offset, len).asInt();
+ }
+
+ public static int farmHash(byte[] data, int offset, int len) {
+ return Hashing.farmHashFingerprint64().hashBytes(data, offset, len).asInt();
+ }
+
+ public static void main(String[] args) {
+ byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ farmHash(data, 0, data.length);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Hash4j.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Hash4j.java
new file mode 100644
index 00000000..0ac09bf6
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Hash4j.java
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import com.dynatrace.hash4j.hashing.Hasher32;
+import com.dynatrace.hash4j.hashing.Hasher64;
+import com.dynatrace.hash4j.hashing.Hashing;
+
+public class Hash4j {
+ private static final Hasher64 XXH_3_64 = Hashing.xxh3_64(0);
+ private static final Hasher64 FARM_HASH_NA = Hashing.farmHashNa();
+ private static final Hasher64 FARM_HASH_UO = Hashing.farmHashUo();
+ private static final Hasher32 MURMUR3 = Hashing.murmur3_32();
+
+ public static long hash_xxh3_64(final byte[] bytes, int start, int length) {
+ return XXH_3_64.hashBytesToLong(bytes, start, length);
+ }
+
+ public static long hash_farm_hash(final byte[] bytes, int start, int length) {
+ return FARM_HASH_NA.hashBytesToLong(bytes, start, length);
+ }
+
+ public static long hash_farm_hash_uo(final byte[] bytes, int start, int length) {
+ return FARM_HASH_UO.hashBytesToLong(bytes, start, length);
+ }
+
+ public static int hash_murmur_3_32(final byte[] bytes, int start, int length) {
+ return MURMUR3.hashBytesToInt(bytes, start, length);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/HighwayHash.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/HighwayHash.java
new file mode 100644
index 00000000..74146dc9
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/HighwayHash.java
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+/**
+ * HighwayHash algorithm. See
+ * HighwayHash on GitHub
+ */
+public final class HighwayHash {
+ private final long[] v0 = new long[4];
+ private final long[] v1 = new long[4];
+ private final long[] mul0 = new long[4];
+ private final long[] mul1 = new long[4];
+ private boolean done = false;
+
+ /**
+ * @param key0 first 8 bytes of the key
+ * @param key1 next 8 bytes of the key
+ * @param key2 next 8 bytes of the key
+ * @param key3 last 8 bytes of the key
+ */
+ public HighwayHash(long key0, long key1, long key2, long key3) {
+ reset(key0, key1, key2, key3);
+ }
+
+ /**
+ * @param key array of size 4 with the key to initialize the hash with
+ */
+ public HighwayHash(long[] key) {
+ if (key.length != 4) {
+ throw new IllegalArgumentException(String.format("Key length (%s) must be 4", key.length));
+ }
+ reset(key[0], key[1], key[2], key[3]);
+ }
+
+ /**
+ * Updates the hash with 32 bytes of data. If you can read 4 long values
+ * from your data efficiently, prefer using update() instead for more speed.
+ * @param packet data array which has a length of at least pos + 32
+ * @param pos position in the array to read the first of 32 bytes from
+ */
+ public void updatePacket(byte[] packet, int pos) {
+ if (pos < 0) {
+ throw new IllegalArgumentException(String.format("Pos (%s) must be positive", pos));
+ }
+ if (pos + 32 > packet.length) {
+ throw new IllegalArgumentException("packet must have at least 32 bytes after pos");
+ }
+ long a0 = read64(packet, pos + 0);
+ long a1 = read64(packet, pos + 8);
+ long a2 = read64(packet, pos + 16);
+ long a3 = read64(packet, pos + 24);
+ update(a0, a1, a2, a3);
+ }
+
+ /**
+ * Updates the hash with 32 bytes of data given as 4 longs. This function is
+ * more efficient than updatePacket when you can use it.
+ * @param a0 first 8 bytes in little endian 64-bit long
+ * @param a1 next 8 bytes in little endian 64-bit long
+ * @param a2 next 8 bytes in little endian 64-bit long
+ * @param a3 last 8 bytes in little endian 64-bit long
+ */
+ public void update(long a0, long a1, long a2, long a3) {
+ if (done) {
+ throw new IllegalStateException("Can compute a hash only once per instance");
+ }
+ v1[0] += mul0[0] + a0;
+ v1[1] += mul0[1] + a1;
+ v1[2] += mul0[2] + a2;
+ v1[3] += mul0[3] + a3;
+ for (int i = 0; i < 4; ++i) {
+ mul0[i] ^= (v1[i] & 0xffffffffL) * (v0[i] >>> 32);
+ v0[i] += mul1[i];
+ mul1[i] ^= (v0[i] & 0xffffffffL) * (v1[i] >>> 32);
+ }
+ v0[0] += zipperMerge0(v1[1], v1[0]);
+ v0[1] += zipperMerge1(v1[1], v1[0]);
+ v0[2] += zipperMerge0(v1[3], v1[2]);
+ v0[3] += zipperMerge1(v1[3], v1[2]);
+ v1[0] += zipperMerge0(v0[1], v0[0]);
+ v1[1] += zipperMerge1(v0[1], v0[0]);
+ v1[2] += zipperMerge0(v0[3], v0[2]);
+ v1[3] += zipperMerge1(v0[3], v0[2]);
+ }
+
+ /**
+ * Updates the hash with the last 1 to 31 bytes of the data. You must use
+ * updatePacket first per 32 bytes of the data, if and only if 1 to 31 bytes
+ * of the data are not processed after that, updateRemainder must be used for
+ * those final bytes.
+ * @param bytes data array which has a length of at least pos + size_mod32
+ * @param pos position in the array to start reading size_mod32 bytes from
+ * @param size_mod32 the amount of bytes to read
+ */
+ public void updateRemainder(byte[] bytes, int pos, int size_mod32) {
+ if (pos < 0) {
+ throw new IllegalArgumentException(String.format("Pos (%s) must be positive", pos));
+ }
+ if (size_mod32 < 0 || size_mod32 >= 32) {
+ throw new IllegalArgumentException(String.format("size_mod32 (%s) must be between 0 and 31", size_mod32));
+ }
+ if (pos + size_mod32 > bytes.length) {
+ throw new IllegalArgumentException("bytes must have at least size_mod32 bytes after pos");
+ }
+ int size_mod4 = size_mod32 & 3;
+ int remainder = size_mod32 & ~3;
+ byte[] packet = new byte[32];
+ for (int i = 0; i < 4; ++i) {
+ v0[i] += ((long) size_mod32 << 32) + size_mod32;
+ }
+ rotate32By(size_mod32, v1);
+ for (int i = 0; i < remainder; i++) {
+ packet[i] = bytes[pos + i];
+ }
+ if ((size_mod32 & 16) != 0) {
+ for (int i = 0; i < 4; i++) {
+ packet[28 + i] = bytes[pos + remainder + i + size_mod4 - 4];
+ }
+ } else {
+ if (size_mod4 != 0) {
+ packet[16 + 0] = bytes[pos + remainder + 0];
+ packet[16 + 1] = bytes[pos + remainder + (size_mod4 >>> 1)];
+ packet[16 + 2] = bytes[pos + remainder + (size_mod4 - 1)];
+ }
+ }
+ updatePacket(packet, 0);
+ }
+
+ /**
+ * Computes the hash value after all bytes were processed. Invalidates the
+ * state.
+ *
+ * NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
+ *
+ * @return 64-bit hash
+ */
+ public long finalize64() {
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ done = true;
+ return v0[0] + v1[0] + mul0[0] + mul1[0];
+ }
+
+ /**
+ * Computes the hash value after all bytes were processed. Invalidates the state.
+ *
+ * @return array of size 2 containing 128-bit hash
+ */
+ public long[] finalize128() {
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ done = true;
+ long[] hash = new long[2];
+ hash[0] = v0[0] + mul0[0] + v1[2] + mul1[2];
+ hash[1] = v0[1] + mul0[1] + v1[3] + mul1[3];
+ return hash;
+ }
+
+ /**
+ * Computes the hash value after all bytes were processed. Invalidates the state.
+ *
+ * @return array of size 4 containing 256-bit hash
+ */
+ public long[] finalize256() {
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ permuteAndUpdate();
+ done = true;
+ long[] hash = new long[4];
+ modularReduction(v1[1] + mul1[1], v1[0] + mul1[0], v0[1] + mul0[1], v0[0] + mul0[0], hash, 0);
+ modularReduction(v1[3] + mul1[3], v1[2] + mul1[2], v0[3] + mul0[3], v0[2] + mul0[2], hash, 2);
+ return hash;
+ }
+
+ private void reset(long key0, long key1, long key2, long key3) {
+ mul0[0] = 0xdbe6d5d5fe4cce2fL;
+ mul0[1] = 0xa4093822299f31d0L;
+ mul0[2] = 0x13198a2e03707344L;
+ mul0[3] = 0x243f6a8885a308d3L;
+ mul1[0] = 0x3bd39e10cb0ef593L;
+ mul1[1] = 0xc0acf169b5f18a8cL;
+ mul1[2] = 0xbe5466cf34e90c6cL;
+ mul1[3] = 0x452821e638d01377L;
+ v0[0] = mul0[0] ^ key0;
+ v0[1] = mul0[1] ^ key1;
+ v0[2] = mul0[2] ^ key2;
+ v0[3] = mul0[3] ^ key3;
+ v1[0] = mul1[0] ^ ((key0 >>> 32) | (key0 << 32));
+ v1[1] = mul1[1] ^ ((key1 >>> 32) | (key1 << 32));
+ v1[2] = mul1[2] ^ ((key2 >>> 32) | (key2 << 32));
+ v1[3] = mul1[3] ^ ((key3 >>> 32) | (key3 << 32));
+ }
+
+ private long zipperMerge0(long v1, long v0) {
+ return (((v0 & 0xff000000L) | (v1 & 0xff00000000L)) >>> 24)
+ | (((v0 & 0xff0000000000L) | (v1 & 0xff000000000000L)) >>> 16)
+ | (v0 & 0xff0000L)
+ | ((v0 & 0xff00L) << 32)
+ | ((v1 & 0xff00000000000000L) >>> 8)
+ | (v0 << 56);
+ }
+
+ private long zipperMerge1(long v1, long v0) {
+ return (((v1 & 0xff000000L) | (v0 & 0xff00000000L)) >>> 24)
+ | (v1 & 0xff0000L)
+ | ((v1 & 0xff0000000000L) >>> 16)
+ | ((v1 & 0xff00L) << 24)
+ | ((v0 & 0xff000000000000L) >>> 8)
+ | ((v1 & 0xffL) << 48)
+ | (v0 & 0xff00000000000000L);
+ }
+
+ private long read64(byte[] src, int pos) {
+ // Mask with 0xffL so that it is 0..255 as long (byte can only be -128..127)
+ return (src[pos] & 0xffL)
+ | ((src[pos + 1] & 0xffL) << 8)
+ | ((src[pos + 2] & 0xffL) << 16)
+ | ((src[pos + 3] & 0xffL) << 24)
+ | ((src[pos + 4] & 0xffL) << 32)
+ | ((src[pos + 5] & 0xffL) << 40)
+ | ((src[pos + 6] & 0xffL) << 48)
+ | ((src[pos + 7] & 0xffL) << 56);
+ }
+
+ private void rotate32By(long count, long[] lanes) {
+ for (int i = 0; i < 4; ++i) {
+ long half0 = (lanes[i] & 0xffffffffL);
+ long half1 = (lanes[i] >>> 32) & 0xffffffffL;
+ lanes[i] = ((half0 << count) & 0xffffffffL) | (half0 >>> (32 - count));
+ lanes[i] |= ((((half1 << count) & 0xffffffffL) | (half1 >>> (32 - count)))) << 32;
+ }
+ }
+
+ private void permuteAndUpdate() {
+ update(
+ (v0[2] >>> 32) | (v0[2] << 32),
+ (v0[3] >>> 32) | (v0[3] << 32),
+ (v0[0] >>> 32) | (v0[0] << 32),
+ (v0[1] >>> 32) | (v0[1] << 32));
+ }
+
+ private void modularReduction(long a3_unmasked, long a2, long a1, long a0, long[] hash, int pos) {
+ long a3 = a3_unmasked & 0x3FFFFFFFFFFFFFFFL;
+ hash[pos + 1] = a1 ^ ((a3 << 1) | (a2 >>> 63)) ^ ((a3 << 2) | (a2 >>> 62));
+ hash[pos] = a0 ^ (a2 << 1) ^ (a2 << 2);
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
+ *
+ * @param data array with data bytes
+ * @param offset position of first byte of data to read from
+ * @param length number of bytes from data to read
+ * @param key array of size 4 with the key to initialize the hash with
+ * @return 64-bit hash for the given data
+ */
+ public static long hash64(byte[] data, int offset, int length, long[] key) {
+ HighwayHash h = new HighwayHash(key);
+ h.processAll(data, offset, length);
+ return h.finalize64();
+ }
+
+ /**
+ * @param data array with data bytes
+ * @param offset position of first byte of data to read from
+ * @param length number of bytes from data to read
+ * @param key array of size 4 with the key to initialize the hash with
+ * @return array of size 2 containing 128-bit hash for the given data
+ */
+ public static long[] hash128(byte[] data, int offset, int length, long[] key) {
+ HighwayHash h = new HighwayHash(key);
+ h.processAll(data, offset, length);
+ return h.finalize128();
+ }
+
+ /**
+ * @param data array with data bytes
+ * @param offset position of first byte of data to read from
+ * @param length number of bytes from data to read
+ * @param key array of size 4 with the key to initialize the hash with
+ * @return array of size 4 containing 256-bit hash for the given data
+ */
+ public static long[] hash256(byte[] data, int offset, int length, long[] key) {
+ HighwayHash h = new HighwayHash(key);
+ h.processAll(data, offset, length);
+ return h.finalize256();
+ }
+
+ private void processAll(byte[] data, int offset, int length) {
+ int i;
+ for (i = 0; i + 32 <= length; i += 32) {
+ updatePacket(data, offset + i);
+ }
+ if ((length & 31) != 0) {
+ updateRemainder(data, offset + i, length & 31);
+ }
+ }
+
+ /**
+ * NOTE: The 64-bit HighwayHash algorithm is declared stable and no longer subject to change.
+ *
+ * @param data array with data bytes
+ * @param offset position of first byte of data to read from
+ * @param length number of bytes from data to read
+ * @return 64-bit hash for the given data
+ */
+ public static long hash64(byte[] data, int offset, int length) {
+ HighwayHash h = new HighwayHash(KEY);
+ h.processAll(data, offset, length);
+ return h.finalize64();
+ }
+
+ // TODO what is best key for HighwayHash?
+ private static final long[] KEY = {1, 2, 3, 4};
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/JavaStyleHashing.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/JavaStyleHashing.java
new file mode 100644
index 00000000..33b94c73
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/JavaStyleHashing.java
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Versions of the traditional Java-style hashing algorithms with different multiplier constants. The 31 constant is
+ * what is used in JDK hashCode() methods, while 255 and 256 are interesting alternatives.
+ */
+public class JavaStyleHashing {
+ public static int hash31(@NonNull final byte[] bytes, int start, int length) {
+ int h = 1;
+ for (int i = length - 1; i >= start; i--) {
+ h = 31 * h + bytes[i];
+ }
+ return h;
+ }
+
+ public static int hash255(@NonNull final byte[] bytes, int start, int length) {
+ int h = 1;
+ for (int i = length - 1; i >= start; i--) {
+ h = 255 * h + bytes[i];
+ }
+ return h;
+ }
+
+ public static int hash256(@NonNull final byte[] bytes, int start, int length) {
+ int h = 1;
+ for (int i = length - 1; i >= start; i--) {
+ h = 256 * h + bytes[i];
+ }
+ return h;
+ }
+
+ public static int hash257(@NonNull final byte[] bytes, int start, int length) {
+ int h = 1;
+ for (int i = length - 1; i >= start; i--) {
+ h = 257 * h + bytes[i];
+ }
+ return h;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/LeemonMurmur.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/LeemonMurmur.java
new file mode 100644
index 00000000..b0928ae5
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/LeemonMurmur.java
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import com.hedera.pbj.runtime.io.UnsafeUtils;
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+/**
+ * Non-cryptographic 64-bit hash function based on Leemon's hash64 with murmurHash3 mixer function.
+ */
+public class LeemonMurmur {
+
+ /**
+ * Generates a non-cryptographic 64-bit hash for contents of the given byte array within indexes position
+ * (inclusive) and position + length (exclusive).
+ *
+ * @param bytes A byte array. Must not be null. Can be empty.
+ * @param position The starting position within the byte array to begin hashing from. Must be non-negative,
+ * and must be less than the length of the array, and position + length must also be
+ * less than or equal to the length of the array.
+ * @param length
+ * The number of bytes to hash. Must be non-negative, and must be such that position + length
+ * is less than or equal to the length of the byte array.
+ *
+ * @return a non-cryptographic long hash
+ */
+ public static long hash64(@NonNull final byte[] bytes, final int position, final int length) {
+ // Accumulate the hash in 64-bit chunks. If the length is not a multiple of 8, then read
+ // as many complete 8 byte chunks as possible.
+ long hash = 1;
+ int i = position;
+ int end = position + length - 7;
+ for (; i < end; i += 8) {
+ hash = murmurHash3Mixer(hash ^ UnsafeUtils.getLongNoChecksLittleEndian(bytes, i));
+ }
+
+ // Construct a trailing long. If the segment of the byte array we read was exactly a multiple of 8 bytes,
+ // then we will append "0x000000000000007F" to the end of the hash. If we had 1 byte remaining, then
+ // we will append "0x0000000000007FXX" where XX is the value of the last byte, and so on.
+ long tail = 0x7F;
+ int start = i;
+ i = position + length - 1;
+ for (; i >= start; i--) {
+ tail <<= 8;
+ tail ^= bytes[i];
+ }
+
+ // Combine the tail with the previous hash.
+ hash = murmurHash3Mixer(hash ^ tail);
+
+ return hash;
+ }
+
+ private static long murmurHash3Mixer(long key) {
+ key ^= (key >> 33);
+ key *= 0xff51afd7ed558ccdL;
+ key ^= (key >> 33);
+ key *= 0xc4ceb9fe1a85ec53L;
+ key ^= (key >> 33);
+ return key;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/LuceneMurmur3.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/LuceneMurmur3.java
new file mode 100644
index 00000000..7b35f59d
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/LuceneMurmur3.java
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * MurmurHash3 is a port of the MurmurHash3 algorithm, which is a non-cryptographic hash function. From Apache Lucene
+ * project.
+ *
+ * @see
+ * Apache Lucene StringHelper
+ */
+public abstract class LuceneMurmur3 {
+ private static final int SEED = 1; // Default seed value
+ private static final VarHandle VH_LE_LONG =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle VH_LE_INT =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+ /**
+ * Returns the MurmurHash3_x86_32 hash. Original source/tests at
+ * ...
+ */
+ @SuppressWarnings("fallthrough")
+ public static int murmurhash3_x86_32(byte[] data, int offset, int len) {
+ final int c1 = 0xcc9e2d51;
+ final int c2 = 0x1b873593;
+
+ int h1 = SEED;
+ int roundedEnd = offset + (len & 0xfffffffc); // round down to 4 byte block
+
+ for (int i = offset; i < roundedEnd; i += 4) {
+ // little endian load order
+ int k1 = (int) VH_LE_INT.get(data, i);
+ k1 *= c1;
+ k1 = Integer.rotateLeft(k1, 15);
+ k1 *= c2;
+
+ h1 ^= k1;
+ h1 = Integer.rotateLeft(h1, 13);
+ h1 = h1 * 5 + 0xe6546b64;
+ }
+
+ // tail
+ int k1 = 0;
+
+ switch (len & 0x03) {
+ case 3:
+ k1 = (data[roundedEnd + 2] & 0xff) << 16;
+ // fallthrough
+ case 2:
+ k1 |= (data[roundedEnd + 1] & 0xff) << 8;
+ // fallthrough
+ case 1:
+ k1 |= (data[roundedEnd] & 0xff);
+ k1 *= c1;
+ k1 = Integer.rotateLeft(k1, 15);
+ k1 *= c2;
+ h1 ^= k1;
+ }
+
+ // finalization
+ h1 ^= len;
+
+ // fmix(h1);
+ h1 ^= h1 >>> 16;
+ h1 *= 0x85ebca6b;
+ h1 ^= h1 >>> 13;
+ h1 *= 0xc2b2ae35;
+ h1 ^= h1 >>> 16;
+
+ return h1;
+ }
+
+ /**
+ * Generates 128-bit hash from the byte array with the given offset, length and seed.
+ *
+ *
The code is adopted from Apache Commons (link)
+ *
+ * @param data The input byte array
+ * @param offset The first element of array
+ * @param length The length of array
+ * @param seed The initial seed value
+ * @return The 128-bit hash (2 longs)
+ */
+ public static long[] murmurhash3_x64_128(final byte[] data, final int offset, final int length, final int seed) {
+ // Use an unsigned 32-bit integer as the seed
+ return murmurhash3_x64_128(data, offset, length, seed & 0xFFFFFFFFL);
+ }
+
+ public static long murmurhash3_x64_128(final byte[] data, final int offset, final int length) {
+ // Use an unsigned 32-bit integer as the seed
+ return murmurhash3_x64_128(data, offset, length, SEED & 0xFFFFFFFFL)[0];
+ }
+
+ @SuppressWarnings("fallthrough")
+ private static long[] murmurhash3_x64_128(final byte[] data, final int offset, final int length, final long seed) {
+ long h1 = seed;
+ long h2 = seed;
+ final int nblocks = length >> 4;
+
+ // Constants for 128-bit variant
+ final long C1 = 0x87c37b91114253d5L;
+ final long C2 = 0x4cf5ad432745937fL;
+ final int R1 = 31;
+ final int R2 = 27;
+ final int R3 = 33;
+ final int M = 5;
+ final int N1 = 0x52dce729;
+ final int N2 = 0x38495ab5;
+
+ // body
+ for (int i = 0; i < nblocks; i++) {
+ final int index = offset + (i << 4);
+ long k1 = (long) VH_LE_LONG.get(data, index);
+ long k2 = (long) VH_LE_LONG.get(data, index + 8);
+
+ // mix functions for k1
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, R1);
+ k1 *= C2;
+ h1 ^= k1;
+ h1 = Long.rotateLeft(h1, R2);
+ h1 += h2;
+ h1 = h1 * M + N1;
+
+ // mix functions for k2
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, R3);
+ k2 *= C1;
+ h2 ^= k2;
+ h2 = Long.rotateLeft(h2, R1);
+ h2 += h1;
+ h2 = h2 * M + N2;
+ }
+
+ // tail
+ long k1 = 0;
+ long k2 = 0;
+ final int index = offset + (nblocks << 4);
+ switch (length & 0x0F) {
+ case 15:
+ k2 ^= ((long) data[index + 14] & 0xff) << 48;
+ case 14:
+ k2 ^= ((long) data[index + 13] & 0xff) << 40;
+ case 13:
+ k2 ^= ((long) data[index + 12] & 0xff) << 32;
+ case 12:
+ k2 ^= ((long) data[index + 11] & 0xff) << 24;
+ case 11:
+ k2 ^= ((long) data[index + 10] & 0xff) << 16;
+ case 10:
+ k2 ^= ((long) data[index + 9] & 0xff) << 8;
+ case 9:
+ k2 ^= data[index + 8] & 0xff;
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, R3);
+ k2 *= C1;
+ h2 ^= k2;
+
+ case 8:
+ k1 ^= ((long) data[index + 7] & 0xff) << 56;
+ case 7:
+ k1 ^= ((long) data[index + 6] & 0xff) << 48;
+ case 6:
+ k1 ^= ((long) data[index + 5] & 0xff) << 40;
+ case 5:
+ k1 ^= ((long) data[index + 4] & 0xff) << 32;
+ case 4:
+ k1 ^= ((long) data[index + 3] & 0xff) << 24;
+ case 3:
+ k1 ^= ((long) data[index + 2] & 0xff) << 16;
+ case 2:
+ k1 ^= ((long) data[index + 1] & 0xff) << 8;
+ case 1:
+ k1 ^= data[index] & 0xff;
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, R1);
+ k1 *= C2;
+ h1 ^= k1;
+ }
+
+ // finalization
+ h1 ^= length;
+ h2 ^= length;
+
+ h1 += h2;
+ h2 += h1;
+
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+
+ h1 += h2;
+ h2 += h1;
+
+ return new long[] {h1, h2};
+ }
+
+ /**
+ * Performs the final avalanche mix step of the 64-bit hash function.
+ *
+ * @param hash The current hash
+ * @return The final hash
+ */
+ private static long fmix64(long hash) {
+ hash ^= (hash >>> 33);
+ hash *= 0xff51afd7ed558ccdL;
+ hash ^= (hash >>> 33);
+ hash *= 0xc4ceb9fe1a85ec53L;
+ hash ^= (hash >>> 33);
+ return hash;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Md5.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Md5.java
new file mode 100644
index 00000000..d529b33a
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Md5.java
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+
+/**
+ * Non-thread-safe MD5 implementation of HashFunction. Takes the lower 32 bits of the hash as integer.
+ */
+public class Md5 {
+ private static MessageDigest md5;
+ private static byte[] hash;
+
+ static {
+ try {
+ md5 = MessageDigest.getInstance("MD5");
+ hash = new byte[md5.getDigestLength()];
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to initialize MD5", e);
+ }
+ }
+
+ public static int hash32(byte[] data, int offset, int len) {
+ md5.update(data, offset, len);
+ try {
+ md5.digest(hash, 0, hash.length);
+ } catch (DigestException e) {
+ throw new RuntimeException(e);
+ }
+ return ((hash[0] & 0xFF) << 24) | ((hash[1] & 0xFF) << 16) | ((hash[2] & 0xFF) << 8) | (hash[3] & 0xFF);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MetroHash64.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MetroHash64.java
new file mode 100644
index 00000000..92c30c65
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MetroHash64.java
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+/*
+ * Copyright (c) 2016 Marius Posta
+ *
+ * Licensed under the Apache 2 license:
+ * http://www.apache.org/licenses/LICENSE-2.0.txt
+ */
+
+import java.nio.ByteBuffer;
+
+public class MetroHash64 {
+
+ public final long seed;
+ private long v0, v1, v2, v3;
+ private long nChunks;
+ private long hash;
+
+ /**
+ * Initializes a MetroHash64 state with the given seed.
+ */
+ public MetroHash64(long seed) {
+ this.seed = seed;
+ reset();
+ }
+
+ public static long hash64(byte[] data, int offset, int length) {
+ MetroHash64 hash64 = new MetroHash64(0);
+ ByteBuffer input = ByteBuffer.wrap(data, offset, length);
+ hash64.reset();
+ while (input.remaining() >= 32) {
+ hash64.partialApply32ByteChunk(input);
+ }
+ return hash64.partialApplyRemaining(input).get();
+ }
+
+ /**
+ * Current hash value.
+ */
+ public long get() {
+ return hash;
+ }
+
+ public MetroHash64 reset() {
+ v0 = v1 = v2 = v3 = hash = (seed + K2) * K0;
+ nChunks = 0;
+ return this;
+ }
+
+ public MetroHash64 partialApply32ByteChunk(ByteBuffer partialInput) {
+ assert partialInput.remaining() >= 32;
+ v0 += grab8(partialInput) * K0;
+ v0 = rotr64(v0, 29) + v2;
+ v1 += grab8(partialInput) * K1;
+ v1 = rotr64(v1, 29) + v3;
+ v2 += grab8(partialInput) * K2;
+ v2 = rotr64(v2, 29) + v0;
+ v3 += grab8(partialInput) * K3;
+ v3 = rotr64(v3, 29) + v1;
+ ++nChunks;
+ return this;
+ }
+
+ public MetroHash64 partialApplyRemaining(ByteBuffer partialInput) {
+ assert partialInput.remaining() < 32;
+ if (nChunks > 0) {
+ metroHash64_32();
+ }
+ if (partialInput.remaining() >= 16) {
+ metroHash64_16(partialInput);
+ }
+ if (partialInput.remaining() >= 8) {
+ metroHash64_8(partialInput);
+ }
+ if (partialInput.remaining() >= 4) {
+ metroHash64_4(partialInput);
+ }
+ if (partialInput.remaining() >= 2) {
+ metroHash64_2(partialInput);
+ }
+ if (partialInput.remaining() >= 1) {
+ metroHash64_1(partialInput);
+ }
+ hash ^= rotr64(hash, 28);
+ hash *= K0;
+ hash ^= rotr64(hash, 29);
+ return this;
+ }
+
+ private static final long K0 = 0xD6D018F5L;
+ private static final long K1 = 0xA2AA033BL;
+ private static final long K2 = 0x62992FC1L;
+ private static final long K3 = 0x30BC5B29L;
+
+ private void metroHash64_32() {
+ v2 ^= rotr64(((v0 + v3) * K0) + v1, 37) * K1;
+ v3 ^= rotr64(((v1 + v2) * K1) + v0, 37) * K0;
+ v0 ^= rotr64(((v0 + v2) * K0) + v3, 37) * K1;
+ v1 ^= rotr64(((v1 + v3) * K1) + v2, 37) * K0;
+ hash += v0 ^ v1;
+ }
+
+ private void metroHash64_16(ByteBuffer bb) {
+ v0 = hash + grab8(bb) * K2;
+ v0 = rotr64(v0, 29) * K3;
+ v1 = hash + grab8(bb) * K2;
+ v1 = rotr64(v1, 29) * K3;
+ v0 ^= rotr64(v0 * K0, 21) + v1;
+ v1 ^= rotr64(v1 * K3, 21) + v0;
+ hash += v1;
+ }
+
+ private void metroHash64_8(ByteBuffer bb) {
+ hash += grab8(bb) * K3;
+ hash ^= rotr64(hash, 55) * K1;
+ }
+
+ private void metroHash64_4(ByteBuffer bb) {
+ hash += grab4(bb) * K3;
+ hash ^= rotr64(hash, 26) * K1;
+ }
+
+ private void metroHash64_2(ByteBuffer bb) {
+ hash += grab2(bb) * K3;
+ hash ^= rotr64(hash, 48) * K1;
+ }
+
+ private void metroHash64_1(ByteBuffer bb) {
+ hash += grab1(bb) * K3;
+ hash ^= rotr64(hash, 37) * K1;
+ }
+
+ static long rotr64(long x, int r) {
+ return (x >>> r) | (x << (64 - r));
+ }
+
+ static long grab1(ByteBuffer bb) {
+ return ((long) bb.get() & 0xFFL);
+ }
+
+ static long grab2(ByteBuffer bb) {
+ final long v0 = bb.get();
+ final long v1 = bb.get();
+ return (v0 & 0xFFL) | (v1 & 0xFFL) << 8;
+ }
+
+ static long grab4(ByteBuffer bb) {
+ final long v0 = bb.get();
+ final long v1 = bb.get();
+ final long v2 = bb.get();
+ final long v3 = bb.get();
+ return (v0 & 0xFFL) | (v1 & 0xFFL) << 8 | (v2 & 0xFFL) << 16 | (v3 & 0xFFL) << 24;
+ }
+
+ static long grab8(ByteBuffer bb) {
+ final long v0 = bb.get();
+ final long v1 = bb.get();
+ final long v2 = bb.get();
+ final long v3 = bb.get();
+ final long v4 = bb.get();
+ final long v5 = bb.get();
+ final long v6 = bb.get();
+ final long v7 = bb.get();
+ return (v0 & 0xFFL)
+ | (v1 & 0xFFL) << 8
+ | (v2 & 0xFFL) << 16
+ | (v3 & 0xFFL) << 24
+ | (v4 & 0xFFL) << 32
+ | (v5 & 0xFFL) << 40
+ | (v6 & 0xFFL) << 48
+ | (v7 & 0xFFL) << 56;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MetroHash64Array.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MetroHash64Array.java
new file mode 100644
index 00000000..06b0f108
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MetroHash64Array.java
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+/*
+ * Copyright (c) 2016 Marius Posta
+ *
+ * Licensed under the Apache 2 license:
+ * http://www.apache.org/licenses/LICENSE-2.0.txt
+ */
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class MetroHash64Array {
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle INT_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+
+ public final long seed;
+ private long v0, v1, v2, v3;
+ private long nChunks;
+ private long hash;
+
+ /**
+ * Initializes a MetroHash64 state with the given seed.
+ */
+ public MetroHash64Array(long seed) {
+ this.seed = seed;
+ reset();
+ }
+
+ public static long hash64(byte[] data, int offset, int length) {
+ MetroHash64Array hash64 = new MetroHash64Array(0);
+ ByteBuffer input = ByteBuffer.wrap(data, offset, length);
+ hash64.reset();
+ while (input.remaining() >= 32) {
+ hash64.partialApply32ByteChunk(input);
+ }
+ return hash64.partialApplyRemaining(input).get();
+ }
+
+ /**
+ * Current hash value.
+ */
+ public long get() {
+ return hash;
+ }
+
+ public MetroHash64Array reset() {
+ v0 = v1 = v2 = v3 = hash = (seed + K2) * K0;
+ nChunks = 0;
+ return this;
+ }
+
+ public MetroHash64Array partialApply32ByteChunk(ByteBuffer partialInput) {
+ assert partialInput.remaining() >= 32;
+ v0 += grab8(partialInput) * K0;
+ v0 = rotr64(v0, 29) + v2;
+ v1 += grab8(partialInput) * K1;
+ v1 = rotr64(v1, 29) + v3;
+ v2 += grab8(partialInput) * K2;
+ v2 = rotr64(v2, 29) + v0;
+ v3 += grab8(partialInput) * K3;
+ v3 = rotr64(v3, 29) + v1;
+ ++nChunks;
+ return this;
+ }
+
+ public MetroHash64Array partialApplyRemaining(ByteBuffer partialInput) {
+ assert partialInput.remaining() < 32;
+ if (nChunks > 0) {
+ metroHash64_32();
+ }
+ if (partialInput.remaining() >= 16) {
+ metroHash64_16(partialInput);
+ }
+ if (partialInput.remaining() >= 8) {
+ metroHash64_8(partialInput);
+ }
+ if (partialInput.remaining() >= 4) {
+ metroHash64_4(partialInput);
+ }
+ if (partialInput.remaining() >= 2) {
+ metroHash64_2(partialInput);
+ }
+ if (partialInput.remaining() >= 1) {
+ metroHash64_1(partialInput);
+ }
+ hash ^= rotr64(hash, 28);
+ hash *= K0;
+ hash ^= rotr64(hash, 29);
+ return this;
+ }
+
+ private static final long K0 = 0xD6D018F5L;
+ private static final long K1 = 0xA2AA033BL;
+ private static final long K2 = 0x62992FC1L;
+ private static final long K3 = 0x30BC5B29L;
+
+ private void metroHash64_32() {
+ v2 ^= rotr64(((v0 + v3) * K0) + v1, 37) * K1;
+ v3 ^= rotr64(((v1 + v2) * K1) + v0, 37) * K0;
+ v0 ^= rotr64(((v0 + v2) * K0) + v3, 37) * K1;
+ v1 ^= rotr64(((v1 + v3) * K1) + v2, 37) * K0;
+ hash += v0 ^ v1;
+ }
+
+ private void metroHash64_16(ByteBuffer bb) {
+ v0 = hash + grab8(bb) * K2;
+ v0 = rotr64(v0, 29) * K3;
+ v1 = hash + grab8(bb) * K2;
+ v1 = rotr64(v1, 29) * K3;
+ v0 ^= rotr64(v0 * K0, 21) + v1;
+ v1 ^= rotr64(v1 * K3, 21) + v0;
+ hash += v1;
+ }
+
+ private void metroHash64_8(ByteBuffer bb) {
+ hash += grab8(bb) * K3;
+ hash ^= rotr64(hash, 55) * K1;
+ }
+
+ private void metroHash64_4(ByteBuffer bb) {
+ hash += grab4(bb) * K3;
+ hash ^= rotr64(hash, 26) * K1;
+ }
+
+ private void metroHash64_2(ByteBuffer bb) {
+ hash += grab2(bb) * K3;
+ hash ^= rotr64(hash, 48) * K1;
+ }
+
+ private void metroHash64_1(ByteBuffer bb) {
+ hash += grab1(bb) * K3;
+ hash ^= rotr64(hash, 37) * K1;
+ }
+
+ static long rotr64(long x, int r) {
+ return (x >>> r) | (x << (64 - r));
+ }
+
+ static long grab1(ByteBuffer bb) {
+ return ((long) bb.get() & 0xFFL);
+ }
+
+ static long grab2(ByteBuffer bb) {
+ final long v0 = bb.get();
+ final long v1 = bb.get();
+ return (v0 & 0xFFL) | (v1 & 0xFFL) << 8;
+ }
+
+ static long grab4(ByteBuffer bb) {
+ final long v0 = bb.get();
+ final long v1 = bb.get();
+ final long v2 = bb.get();
+ final long v3 = bb.get();
+ return (v0 & 0xFFL) | (v1 & 0xFFL) << 8 | (v2 & 0xFFL) << 16 | (v3 & 0xFFL) << 24;
+ }
+
+ static long grab8(ByteBuffer bb) {
+ final long v0 = bb.get();
+ final long v1 = bb.get();
+ final long v2 = bb.get();
+ final long v3 = bb.get();
+ final long v4 = bb.get();
+ final long v5 = bb.get();
+ final long v6 = bb.get();
+ final long v7 = bb.get();
+ return (v0 & 0xFFL)
+ | (v1 & 0xFFL) << 8
+ | (v2 & 0xFFL) << 16
+ | (v3 & 0xFFL) << 24
+ | (v4 & 0xFFL) << 32
+ | (v5 & 0xFFL) << 40
+ | (v6 & 0xFFL) << 48
+ | (v7 & 0xFFL) << 56;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Murmur3Fast.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Murmur3Fast.java
new file mode 100644
index 00000000..636fbcb5
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Murmur3Fast.java
@@ -0,0 +1,403 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * ChatGPT Highly optimized MurmurHash3 x64 128-bit variant folded to 64 bits (h1 + h2),
+ * seed fixed at 1 to match Murmur3OpenHFT.hash64(...). Produces identical results
+ * (bit-for-bit) to the OpenHFT implementation for all inputs while targeting lower latency.
+ *
+ * Design choices:
+ * - Specialized fast paths for <=16 and <=32 bytes.
+ * - 32-byte unrolled main loop (two 16-byte blocks per iteration).
+ * - Inlined mixK1 / mixK2 logic.
+ * - Tail switch identical in semantics to canonical implementation.
+ * - Uses VarHandle little-endian views for aligned-ish bulk loads.
+ *
+ * Public domain (matching original Murmur3 licensing spirit).
+ */
+public final class Murmur3Fast {
+ private static final long SEED = 1L;
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+
+ private static final long C1 = 0x87c37b91114253d5L;
+ private static final long C2 = 0x4cf5ad432745937fL;
+
+ private Murmur3Fast() {}
+
+ /**
+ * Compute MurmurHash3 x64 128-bit variant folded to a single 64-bit value (h1 + h2),
+ * identical to OpenHFT MurmurHash_3.hash128(...).low() + high() approach used there.
+ *
+ * @param data byte array
+ * @param offset starting offset
+ * @param length number of bytes
+ * @return 64-bit hash
+ */
+ @SuppressWarnings("fallthrough")
+ public static long hash64(byte[] data, int offset, int length) {
+ // Fast paths for very small inputs (avoid loop / extra branches)
+ if (length <= 16) {
+ return smallHash16(data, offset, length);
+ }
+ if (length <= 32) {
+ return smallHash32(data, offset, length);
+ }
+
+ long h1 = SEED;
+ long h2 = SEED;
+
+ int pos = offset;
+ int end = offset + length;
+ int remaining = length;
+
+ // Process 32 bytes per iteration (two standard 16-byte Murmur blocks)
+ while (remaining >= 32) {
+ // First 16 bytes
+ long k1 = load64(data, pos);
+ long k2 = load64(data, pos + 8);
+ pos += 16;
+ remaining -= 16;
+
+ // mix block into h1/h2 (inlined mixK1/k2)
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ h1 ^= k1;
+ h1 = Long.rotateLeft(h1, 27);
+ h1 += h2;
+ h1 = h1 * 5 + 0x52dce729L;
+
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ h2 ^= k2;
+ h2 = Long.rotateLeft(h2, 31);
+ h2 += h1;
+ h2 = h2 * 5 + 0x38495ab5L;
+
+ // Second 16 bytes (only if we still have at least 16; we already knew remaining >=16 at top)
+ k1 = load64(data, pos);
+ k2 = load64(data, pos + 8);
+ pos += 16;
+ remaining -= 16;
+
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ h1 ^= k1;
+ h1 = Long.rotateLeft(h1, 27);
+ h1 += h2;
+ h1 = h1 * 5 + 0x52dce729L;
+
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ h2 ^= k2;
+ h2 = Long.rotateLeft(h2, 31);
+ h2 += h1;
+ h2 = h2 * 5 + 0x38495ab5L;
+ }
+
+ // Any leftover full 16-byte block
+ while (remaining >= 16) {
+ long k1 = load64(data, pos);
+ long k2 = load64(data, pos + 8);
+ pos += 16;
+ remaining -= 16;
+
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ h1 ^= k1;
+ h1 = Long.rotateLeft(h1, 27);
+ h1 += h2;
+ h1 = h1 * 5 + 0x52dce729L;
+
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ h2 ^= k2;
+ h2 = Long.rotateLeft(h2, 31);
+ h2 += h1;
+ h2 = h2 * 5 + 0x38495ab5L;
+ }
+
+ // Tail (0..15 bytes)
+ if (remaining > 0) {
+ long k1 = 0;
+ long k2 = 0;
+ // Build identical to canonical switch on remaining
+ switch (remaining) {
+ case 15:
+ k2 ^= (long) (data[pos + 14] & 0xFF) << 48;
+ case 14:
+ k2 ^= (long) (data[pos + 13] & 0xFF) << 40;
+ case 13:
+ k2 ^= (long) (data[pos + 12] & 0xFF) << 32;
+ case 12:
+ k2 ^= (long) (data[pos + 11] & 0xFF) << 24;
+ case 11:
+ k2 ^= (long) (data[pos + 10] & 0xFF) << 16;
+ case 10:
+ k2 ^= (long) (data[pos + 9] & 0xFF) << 8;
+ case 9:
+ k2 ^= (long) (data[pos + 8] & 0xFF);
+ case 8:
+ k1 ^= load64(data, pos);
+ break;
+ case 7:
+ k1 ^= (long) (data[pos + 6] & 0xFF) << 48;
+ case 6:
+ k1 ^= (long) (data[pos + 5] & 0xFF) << 40;
+ case 5:
+ k1 ^= (long) (data[pos + 4] & 0xFF) << 32;
+ case 4:
+ k1 ^= (load32(data, pos) & 0xFFFFFFFFL);
+ break;
+ case 3:
+ k1 ^= (long) (data[pos + 2] & 0xFF) << 16;
+ case 2:
+ k1 ^= (long) (data[pos + 1] & 0xFF) << 8;
+ case 1:
+ k1 ^= (long) (data[pos] & 0xFF);
+ case 0:
+ break;
+ default: // unreachable
+ }
+
+ if (remaining > 8) {
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ h2 ^= k2;
+ }
+ if (remaining > 0) {
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ h1 ^= k1;
+ }
+ }
+
+ // Finalization (same sequence)
+ h1 ^= length;
+ h2 ^= length;
+ h1 += h2;
+ h2 += h1;
+
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+
+ return h1 + h2;
+ }
+
+ /* ------------ Small-size specialized paths ------------ */
+
+ // For length 1..16 (or 0) – interpret as pure tail on empty loop.
+ private static long smallHash16(byte[] data, int offset, int length) {
+ long h1 = SEED;
+ long h2 = SEED;
+
+ if (length == 0) {
+ // finalize directly
+ h1 ^= 0;
+ h2 ^= 0;
+ h1 += h2;
+ h2 += h1;
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+ return h1 + h2;
+ }
+
+ long k1 = 0;
+ long k2 = 0;
+ // For len > 8, part goes to k2 just like standard tail
+ if (length > 8) {
+ int tailOff = offset + length - 8;
+ k2 = load64LEPartial(data, tailOff, length - 8); // build k2 from last (length-8) bytes
+ k1 = load64LEPartial(data, offset, 8);
+ } else {
+ k1 = load64LEPartial(data, offset, length);
+ }
+
+ if (length > 8) {
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ h2 ^= k2;
+ }
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ h1 ^= k1;
+
+ h1 ^= length;
+ h2 ^= length;
+ h1 += h2;
+ h2 += h1;
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+ return h1 + h2;
+ }
+
+ // For 17..32 bytes: process first 16 as a block, rest as tail
+ @SuppressWarnings("fallthrough")
+ private static long smallHash32(byte[] data, int offset, int length) {
+ long h1 = SEED;
+ long h2 = SEED;
+
+ // First 16 bytes block
+ long k1 = load64(data, offset);
+ long k2 = load64(data, offset + 8);
+
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ h1 ^= k1;
+ h1 = Long.rotateLeft(h1, 27);
+ h1 += h2;
+ h1 = h1 * 5 + 0x52dce729L;
+
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ h2 ^= k2;
+ h2 = Long.rotateLeft(h2, 31);
+ h2 += h1;
+ h2 = h2 * 5 + 0x38495ab5L;
+
+ int remaining = length - 16;
+ if (remaining > 0) {
+ int pos = offset + 16;
+ long t1 = 0;
+ long t2 = 0; // will remain zero (we know remaining <=16)
+
+ // Construct t1 from remaining bytes
+ // Use switch like canonical tail (remaining 1..16)
+ switch (remaining) {
+ case 15:
+ t2 ^= (long) (data[pos + 14] & 0xFF) << 48;
+ case 14:
+ t2 ^= (long) (data[pos + 13] & 0xFF) << 40;
+ case 13:
+ t2 ^= (long) (data[pos + 12] & 0xFF) << 32;
+ case 12:
+ t2 ^= (long) (data[pos + 11] & 0xFF) << 24;
+ case 11:
+ t2 ^= (long) (data[pos + 10] & 0xFF) << 16;
+ case 10:
+ t2 ^= (long) (data[pos + 9] & 0xFF) << 8;
+ case 9:
+ t2 ^= (long) (data[pos + 8] & 0xFF);
+ case 8:
+ t1 ^= load64(data, pos);
+ break;
+ case 7:
+ t1 ^= (long) (data[pos + 6] & 0xFF) << 48;
+ case 6:
+ t1 ^= (long) (data[pos + 5] & 0xFF) << 40;
+ case 5:
+ t1 ^= (long) (data[pos + 4] & 0xFF) << 32;
+ case 4:
+ t1 ^= (load32(data, pos) & 0xFFFFFFFFL);
+ break;
+ case 3:
+ t1 ^= (long) (data[pos + 2] & 0xFF) << 16;
+ case 2:
+ t1 ^= (long) (data[pos + 1] & 0xFF) << 8;
+ case 1:
+ t1 ^= (long) (data[pos] & 0xFF);
+ case 0:
+ break;
+ }
+
+ if (remaining > 8) {
+ t2 *= C2;
+ t2 = Long.rotateLeft(t2, 33);
+ t2 *= C1;
+ h2 ^= t2;
+ }
+ if (remaining > 0) {
+ t1 *= C1;
+ t1 = Long.rotateLeft(t1, 31);
+ t1 *= C2;
+ h1 ^= t1;
+ }
+ }
+
+ h1 ^= length;
+ h2 ^= length;
+ h1 += h2;
+ h2 += h1;
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+ return h1 + h2;
+ }
+
+ /* ------------ Helpers ------------ */
+
+ private static long fmix64(long k) {
+ k ^= k >>> 33;
+ k *= 0xff51afd7ed558ccdL;
+ k ^= k >>> 33;
+ k *= 0xc4ceb9fe1a85ec53L;
+ k ^= k >>> 33;
+ return k;
+ }
+
+ private static long load64(byte[] a, int off) {
+ return (long) LONG_HANDLE.get(a, off);
+ }
+
+ /**
+ * Load 4 bytes from the provided array at the indicated offset.
+ *
+ * @param source the input bytes
+ * @param offset the offset into the array at which to start
+ * @return the value found in the array in the form of a long
+ */
+ private static int load32(byte[] source, int offset) {
+ // This is faster than using VarHandle for 4 bytes
+ return (source[offset] & 0xFF)
+ | ((source[offset + 1] & 0xFF) << 8)
+ | ((source[offset + 2] & 0xFF) << 16)
+ | ((source[offset + 3] & 0xFF) << 24);
+ }
+
+ /**
+ * Assemble up to 8 bytes (count 1..8) little-endian into a long.
+ * When count==8 you should prefer load64 for speed; this is only for partial tails.
+ */
+ @SuppressWarnings("fallthrough")
+ private static long load64LEPartial(byte[] a, int off, int count) {
+ long r = 0;
+ // Unrolled up to 8 for speed (count <=8 guaranteed)
+ switch (count) {
+ case 8:
+ r |= (long) (a[off + 7] & 0xFF) << 56;
+ case 7:
+ r |= (long) (a[off + 6] & 0xFF) << 48;
+ case 6:
+ r |= (long) (a[off + 5] & 0xFF) << 40;
+ case 5:
+ r |= (long) (a[off + 4] & 0xFF) << 32;
+ case 4:
+ r |= (long) (a[off + 3] & 0xFF) << 24;
+ case 3:
+ r |= (long) (a[off + 2] & 0xFF) << 16;
+ case 2:
+ r |= (long) (a[off + 1] & 0xFF) << 8;
+ case 1:
+ r |= (a[off] & 0xFF);
+ case 0:
+ break;
+ default: // not possible
+ }
+ return r;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Murmur3OpenHFT.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Murmur3OpenHFT.java
new file mode 100644
index 00000000..2e29b73a
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Murmur3OpenHFT.java
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * MurmurHash3 implementation in Java, specifically the OpenHFT variant but ported to use VarHandles and hard coded to
+ * byte[] inputs.
+ *
+ * @see
+ * Original OpenHFT MurmurHash3 Source
+ */
+public final class Murmur3OpenHFT {
+ private static final long SEED = 1L; // Default seed value
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+
+ private static final long C1 = 0x87c37b91114253d5L;
+ private static final long C2 = 0x4cf5ad432745937fL;
+
+ @SuppressWarnings("fallthrough")
+ public static long hash64(final byte[] input, int offset, final long length) {
+ long h1 = SEED;
+ long h2 = SEED;
+ long remaining = length;
+ while (remaining >= 16L) {
+ long k1 = i64(input, offset);
+ long k2 = i64(input, offset + 8L);
+ offset += 16;
+ remaining -= 16L;
+ h1 ^= mixK1(k1);
+
+ h1 = Long.rotateLeft(h1, 27);
+ h1 += h2;
+ h1 = h1 * 5L + 0x52dce729L;
+
+ h2 ^= mixK2(k2);
+
+ h2 = Long.rotateLeft(h2, 31);
+ h2 += h1;
+ h2 = h2 * 5L + 0x38495ab5L;
+ }
+
+ if (remaining > 0L) {
+ long k1 = 0L;
+ long k2 = 0L;
+ switch ((int) remaining) {
+ case 15:
+ k2 ^= ((long) u8(input, offset + 14L)) << 48; // fall through
+ case 14:
+ k2 ^= ((long) u8(input, offset + 13L)) << 40; // fall through
+ case 13:
+ k2 ^= ((long) u8(input, offset + 12L)) << 32; // fall through
+ case 12:
+ k2 ^= ((long) u8(input, offset + 11L)) << 24; // fall through
+ case 11:
+ k2 ^= ((long) u8(input, offset + 10L)) << 16; // fall through
+ case 10:
+ k2 ^= ((long) u8(input, offset + 9L)) << 8; // fall through
+ case 9:
+ k2 ^= ((long) u8(input, offset + 8L)); // fall through
+ case 8:
+ k1 ^= i64(input, offset);
+ break;
+ case 7:
+ k1 ^= ((long) u8(input, offset + 6L)) << 48; // fall through
+ case 6:
+ k1 ^= ((long) u8(input, offset + 5L)) << 40; // fall through
+ case 5:
+ k1 ^= ((long) u8(input, offset + 4L)) << 32; // fall through
+ case 4:
+ k1 ^= u32(input, offset);
+ break;
+ case 3:
+ k1 ^= ((long) u8(input, offset + 2L)) << 16; // fall through
+ case 2:
+ k1 ^= ((long) u8(input, offset + 1L)) << 8; // fall through
+ case 1:
+ k1 ^= u8(input, offset);
+ case 0:
+ break;
+ default:
+ throw new AssertionError("Should never get here.");
+ }
+ h1 ^= mixK1(k1);
+ h2 ^= mixK2(k2);
+ }
+ return finalize(length, h1, h2);
+ }
+
+ private static long finalize(long length, long h1, long h2) {
+ h1 ^= length;
+ h2 ^= length;
+ h1 += h2;
+ h2 += h1;
+ h1 = fmix64(h1);
+ h2 = fmix64(h2);
+ return h1 + h2;
+ }
+
+ private static long fmix64(long k) {
+ k ^= k >>> 33;
+ k *= 0xff51afd7ed558ccdL;
+ k ^= k >>> 33;
+ k *= 0xc4ceb9fe1a85ec53L;
+ k ^= k >>> 33;
+ return k;
+ }
+
+ private static long mixK1(long k1) {
+ k1 *= C1;
+ k1 = Long.rotateLeft(k1, 31);
+ k1 *= C2;
+ return k1;
+ }
+
+ private static long mixK2(long k2) {
+ k2 *= C2;
+ k2 = Long.rotateLeft(k2, 33);
+ k2 *= C1;
+ return k2;
+ }
+
+ /**
+ * Reads a 64 bit long in little-endian order from the given byte array at the specified offset.
+ * *
+ * @param input the object to access
+ * @param offset offset to the first byte to read within the byte sequence represented
+ * @return a 64 bit long value, little-endian encoded
+ */
+ private static long i64(final byte[] input, final long offset) {
+ return (long) LONG_HANDLE.get(input, (int) offset);
+ }
+
+ /**
+ * Reads an unsigned byte from the given byte array at the specified offset.
+ *
+ * @param input the object to access
+ * @param offset offset to the byte to read within the byte sequence represented
+ * by the given object
+ * @return a byte by the given {@code offset}, interpreted as unsigned
+ */
+ private static int u8(final byte[] input, final long offset) {
+ return Byte.toUnsignedInt(input[(int) offset]);
+ }
+
+ //
+ // /**
+ // * Shortcut for {@code getInt(input, offset) & 0xFFFFFFFFL}. Could be implemented more
+ // * efficiently.
+ // *
+ // * @param input the object to access
+ // * @param offset offset to the first byte to read within the byte sequence represented
+ // * by the given object
+ // * @return four bytes as an unsigned int value, little-endian encoded
+ // */
+ // private static long u32(final byte[] input, final long offset) {
+ // return (long) INT_HANDLE.get(input, (int)offset) & 0xFFFFFFFFL;
+ // }
+ /**
+ * Load 4 bytes from the provided array at the indicated offset.
+ *
+ * @param source the input bytes
+ * @param offset the offset into the array at which to start
+ * @return the value found in the array in the form of a long
+ */
+ private static int u32(byte[] source, int offset) {
+ // This is faster than using VarHandle for 4 bytes
+ return (source[offset] & 0xFF)
+ | ((source[offset + 1] & 0xFF) << 8)
+ | ((source[offset + 2] & 0xFF) << 16)
+ | ((source[offset + 3] & 0xFF) << 24);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MurmurHash3.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MurmurHash3.java
new file mode 100644
index 00000000..7fa0794b
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/MurmurHash3.java
@@ -0,0 +1,78 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+/**
+ * The MurmurHash3 algorithm was created by Austin Appleby and placed in the public domain.
+ * This java port was authored by Yonik Seeley and also placed into the public domain.
+ * It has been modified by Konstantin Sobolev and, you guessed it, also placed in the public domain.
+ * The author hereby disclaims copyright to this source code.
+ *
+ * This produces exactly the same hash values as the final C++
+ * version of MurmurHash3 and is thus suitable for producing the same hash values across
+ * platforms.
+ *
+ *
+ * @see
+ * Original Java Port Source
+ */
+@SuppressWarnings("fallthrough")
+public final class MurmurHash3 {
+ private static final int c1 = 0xcc9e2d51;
+ private static final int c2 = 0x1b873593;
+
+ /**
+ * Computes the MurmurHash3_x86_32 hash of the given byte array, using seed of 1.
+ *
+ * @param data the byte array to hash
+ * @param offset the starting offset in the byte array
+ * @param len the length of the data to hash
+ * @return the computed hash value
+ */
+ public static int murmurhash3_x86_32(byte[] data, int offset, int len) {
+ int h1 = 1;
+ int roundedEnd = offset + (len & 0xfffffffc); // round down to 4 byte block
+
+ for (int i = offset; i < roundedEnd; i += 4) {
+ // little endian load order
+ int k1 =
+ (data[i] & 0xff) | ((data[i + 1] & 0xff) << 8) | ((data[i + 2] & 0xff) << 16) | (data[i + 3] << 24);
+ k1 *= c1;
+ k1 = (k1 << 15) | (k1 >>> 17); // ROTL32(k1,15);
+ k1 *= c2;
+
+ h1 ^= k1;
+ h1 = (h1 << 13) | (h1 >>> 19); // ROTL32(h1,13);
+ h1 = h1 * 5 + 0xe6546b64;
+ }
+
+ // tail
+ int k1 = 0;
+
+ switch (len & 0x03) {
+ case 3:
+ k1 = (data[roundedEnd + 2] & 0xff) << 16;
+ // fall through
+ case 2:
+ k1 |= (data[roundedEnd + 1] & 0xff) << 8;
+ // fall through
+ case 1:
+ k1 |= (data[roundedEnd] & 0xff);
+ k1 *= c1;
+ k1 = (k1 << 15) | (k1 >>> 17); // ROTL32(k1,15);
+ k1 *= c2;
+ h1 ^= k1;
+ }
+
+ // finalization
+ h1 ^= len;
+
+ // fmix(h1);
+ h1 ^= h1 >>> 16;
+ h1 *= 0x85ebca6b;
+ h1 ^= h1 >>> 13;
+ h1 *= 0xc2b2ae35;
+ h1 ^= h1 >>> 16;
+
+ return h1;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/OlegHash.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/OlegHash.java
new file mode 100644
index 00000000..e0d4e39b
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/OlegHash.java
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import edu.umd.cs.findbugs.annotations.NonNull;
+
+public class OlegHash {
+ static final int[] preHashed;
+
+ static {
+ preHashed = new int[256];
+ for (int b = 0; b < 256; ++b) {
+ int hash = 0;
+ for (int m = 1 << 7; m != 0; m >>= 1) {
+ hash <<= 1;
+ if ((b & m) != 0) {
+ hash ^= 0x8b;
+ }
+ }
+ preHashed[b] = hash;
+ }
+ }
+
+ public static int hash32(@NonNull final byte[] bytes, final int start, final int length) {
+ int hash = 0;
+ for (int i = start; i < start + length; ++i) {
+ hash = (hash << 8) ^ preHashed[(hash >> 24) & 0xff] ^ (bytes[i] & 0xff);
+ }
+ return hash;
+ }
+
+ public static int hash32_2old(byte[] bytes, final int start, final int length) {
+ int hash = 0;
+ for (int i = start; i < start + length; ++i) {
+ hash = (hash << 8) + (hash >>> 24) + (bytes[i] & 0xff);
+ }
+ return hash;
+ }
+
+ public static int hash32_2(byte[] bytes, final int start, final int length) {
+ int hash = 0;
+ for (int i = start; i < start + length; ++i) {
+ hash = (hash << 8) + (hash >>> 24) * 3 + (bytes[i] & 0xff);
+ }
+ return hash;
+ }
+
+ public static long hash64(@NonNull final byte[] bytes, final int start, final int length) {
+ long hash = 0;
+ for (int i = start; i < start + length; ++i) {
+ hash = (hash << 8) + (hash >>> 56) + (bytes[i] & 0xff);
+ }
+ return hash;
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/RapidHash3.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/RapidHash3.java
new file mode 100644
index 00000000..9ec07202
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/RapidHash3.java
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.nio.ByteOrder;
+
+/**
+ * Minimalized port of https://github.com/dynatrace-oss/hash4j/blob/main/src/main/java/com/dynatrace/hash4j/hashing/Rapidhash3.java
+ *
+ * This file includes a Java port of the Rapidhash algorithm originally published
+ * at https://github.com/Nicoshev/rapidhash under the following license:
+ *
+ * Copyright 2025 Nicolas De Carli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+public final class RapidHash3 {
+ private static final VarHandle LONG_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);
+ private static final VarHandle INT_HANDLE =
+ MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);
+
+ private static final long SEC0 = 0x2d358dccaa6c78a5L;
+ private static final long SEC1 = 0x8bb84b93962eacc9L;
+ private static final long SEC2 = 0x4b33a62ed433d4a3L;
+ private static final long SEC3 = 0x4d5a2da51de1aa47L;
+ private static final long SEC4 = 0xa0761d6478bd642fL;
+ private static final long SEC5 = 0xe7037ed1a0b428dbL;
+ private static final long SEC6 = 0x90ed1765281c388cL;
+ private static final long SEC7 = 0xaaaaaaaaaaaaaaaaL;
+
+ private static final long SEED;
+
+ static {
+ final long startSeed = 0L;
+ SEED = startSeed ^ mix(startSeed ^ SEC2, SEC1);
+ }
+
+ /**
+ * Returns the most significant 64 bits of the unsigned 128-bit product of two unsigned 64-bit
+ * factors as a long.
+ *
+ * @param x the first value
+ * @param y the second value
+ * @return the result
+ */
+ private static long unsignedMultiplyHigh(long x, long y) {
+ return Math.multiplyHigh(x, y) + ((x >> 63) & y) + ((y >> 63) & x);
+ }
+
+ private static long mix(long a, long b) {
+ long x = a * b;
+ long y = unsignedMultiplyHigh(a, b);
+ return x ^ y;
+ }
+
+ /**
+ * Reads a {@code long} value from a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @return the read value
+ */
+ private static long getLong(byte[] b, int off) {
+ return (long) LONG_HANDLE.get(b, off);
+ }
+
+ /**
+ * Reads an {@code int} value from a byte array with given offset.
+ *
+ * @param b a byte array
+ * @param off an offset
+ * @return the read value
+ */
+ public static int getInt(byte[] b, int off) {
+ return (int) INT_HANDLE.get(b, off);
+ }
+
+ /**
+ * Hashes the given byte array to a long value using the RapidHash3 algorithm.
+ *
+ * @param input the byte array to hash
+ * @param off the offset in the byte array to start hashing from
+ * @param len the length of the byte array to hash
+ * @return the resulting hash as a long value
+ */
+ public static long hashBytesToLong(byte[] input, int off, int len) {
+ long see0 = SEED;
+ long a;
+ long b;
+ if (len <= 16) {
+ if (len >= 4) {
+ if (len >= 8) {
+ a = getLong(input, off);
+ b = getLong(input, off + len - 8);
+ } else {
+ b = getInt(input, off) & 0xFFFFFFFFL;
+ a = getInt(input, off + len - 4) & 0xFFFFFFFFL;
+ }
+ a ^= len;
+ see0 ^= len;
+ } else if (len > 0) {
+ a = ((input[off] & 0xFFL) << 45) ^ (input[off + len - 1] & 0xFFL) ^ len;
+ b = input[off + (len >> 1)] & 0xFFL;
+ } else {
+ a = 0;
+ b = 0;
+ }
+ } else {
+ long see1 = see0;
+ long see2 = see0;
+ long see3 = see0;
+ long see4 = see0;
+ long see5 = see0;
+ long see6 = see0;
+ if (len > 112) {
+ do {
+ see0 = mix(getLong(input, off) ^ SEC0, getLong(input, off + 8) ^ see0);
+ see1 = mix(getLong(input, off + 16) ^ SEC1, getLong(input, off + 24) ^ see1);
+ see2 = mix(getLong(input, off + 32) ^ SEC2, getLong(input, off + 40) ^ see2);
+ see3 = mix(getLong(input, off + 48) ^ SEC3, getLong(input, off + 56) ^ see3);
+ see4 = mix(getLong(input, off + 64) ^ SEC4, getLong(input, off + 72) ^ see4);
+ see5 = mix(getLong(input, off + 80) ^ SEC5, getLong(input, off + 88) ^ see5);
+ see6 = mix(getLong(input, off + 96) ^ SEC6, getLong(input, off + 104) ^ see6);
+ off += 112;
+ len -= 112;
+ } while (len > 112);
+ see0 ^= see1;
+ see2 ^= see3;
+ see4 ^= see5;
+ see0 ^= see6;
+ see2 ^= see4;
+ see0 ^= see2;
+ }
+ if (len > 16) {
+ see0 = mix(getLong(input, off) ^ SEC2, getLong(input, off + 8) ^ see0);
+ if (len > 32) {
+ see0 = mix(getLong(input, off + 16) ^ SEC2, getLong(input, off + 24) ^ see0);
+ if (len > 48) {
+ see0 = mix(getLong(input, off + 32) ^ SEC1, getLong(input, off + 40) ^ see0);
+ if (len > 64) {
+ see0 = mix(getLong(input, off + 48) ^ SEC1, getLong(input, off + 56) ^ see0);
+ if (len > 80) {
+ see0 = mix(getLong(input, off + 64) ^ SEC2, getLong(input, off + 72) ^ see0);
+ if (len > 96) {
+ see0 = mix(getLong(input, off + 80) ^ SEC1, getLong(input, off + 88) ^ see0);
+ }
+ }
+ }
+ }
+ }
+ }
+ a = getLong(input, off + len - 16);
+ b = getLong(input, off + len - 8);
+ }
+ long a1 = a;
+ long b1 = b;
+ long len1 = len;
+ len1 ^= SEC1;
+ a1 ^= len1;
+ b1 ^= see0;
+ return mix((a1 * b1) ^ SEC7, unsignedMultiplyHigh(a1, b1) ^ len1);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Sha256.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Sha256.java
new file mode 100644
index 00000000..baaef951
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/Sha256.java
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import java.security.DigestException;
+import java.security.MessageDigest;
+
+/**
+ * Non-thread-safe SHA-256 implementation of HashFunction. Takes the lower 32 bits of the hash as integer.
+ */
+public class Sha256 {
+ private static MessageDigest sha256;
+ private static byte[] hash = new byte[32]; // SHA-256 produces a 32-byte hash
+
+ static {
+ try {
+ sha256 = MessageDigest.getInstance("SHA-256");
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to initialize SHA-256", e);
+ }
+ }
+
+ public static int hash32(byte[] data, int offset, int len) {
+ sha256.update(data, offset, len);
+ try {
+ sha256.digest(hash, 0, hash.length);
+ } catch (DigestException e) {
+ throw new RuntimeException(e);
+ }
+ return ((hash[0] & 0xFF) << 24) | ((hash[1] & 0xFF) << 16) | ((hash[2] & 0xFF) << 8) | (hash[3] & 0xFF);
+ }
+}
diff --git a/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/XXH3OpenHFT.java b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/XXH3OpenHFT.java
new file mode 100644
index 00000000..f86648c4
--- /dev/null
+++ b/pbj-integration-tests/src/jmh/java/com/hedera/pbj/integration/jmh/hashing/functions/XXH3OpenHFT.java
@@ -0,0 +1,1136 @@
+// SPDX-License-Identifier: Apache-2.0
+package com.hedera.pbj.integration.jmh.hashing.functions;
+
+import static java.nio.ByteOrder.BIG_ENDIAN;
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+import static java.nio.ByteOrder.nativeOrder;
+
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import sun.misc.Unsafe;
+
+/**
+ * XXH3 is a non-cryptographic hash function designed for speed and quality. Ported from
+ * OpenHFT.
+ * Adapted version of XXH3 implementation from xxHash.
+ * This implementation provides endian-independent hash values, but it's slower on big-endian platforms.
+ */
+public class XXH3OpenHFT {
+ private static final Access