From 318e535178b8668162e68006be475440c0f0be7d Mon Sep 17 00:00:00 2001 From: Roberto Franchini Date: Sat, 25 Jan 2025 08:05:55 +0100 Subject: [PATCH 1/3] #1933 remove use of sun.misc.Unsafe --- .../serializer/UnsignedBytesComparator.java | 103 +++--------------- 1 file changed, 15 insertions(+), 88 deletions(-) diff --git a/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java b/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java index 52dc2a25dd..2c0b1ad816 100644 --- a/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java +++ b/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java @@ -1,29 +1,7 @@ -/* - * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd (info@arcadedata.com) - * SPDX-License-Identifier: Apache-2.0 - */ package com.arcadedb.serializer; -import com.arcadedb.exception.ArcadeDBException; -import sun.misc.Unsafe; - -import java.lang.reflect.*; -import java.nio.*; -import java.security.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * This class was inspired by Guava's UnsignedBytes, under Apache 2 license. @@ -35,29 +13,13 @@ */ public final class UnsignedBytesComparator { private static final int UNSIGNED_MASK = 0xFF; - private static final Unsafe theUnsafe; - private static final int BYTE_ARRAY_BASE_OFFSET; public static final PureJavaComparator PURE_JAVA_COMPARATOR = new PureJavaComparator(); - public static final ByteArrayComparator BEST_COMPARATOR; - - static { - theUnsafe = getUnsafe(); - BYTE_ARRAY_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); - // fall back to the safer pure java implementation unless we're in - // a 64-bit JVM with an 8-byte aligned field offset. - if (!("64".equals(System.getProperty("sun.arch.data.model")) && (BYTE_ARRAY_BASE_OFFSET % 8) == 0 - // sanity check - this should never fail - && theUnsafe.arrayIndexScale(byte[].class) == 1)) { - BEST_COMPARATOR = PURE_JAVA_COMPARATOR; // force fallback to PureJavaComparator - } else { - BEST_COMPARATOR = new UnsafeComparator(); - } - } + public static final ByteArrayComparator BEST_COMPARATOR = new ByteBufferComparator(); private UnsignedBytesComparator() { } - public static class UnsafeComparator implements ByteArrayComparator { + public static class ByteBufferComparator implements ByteArrayComparator { static final boolean BIG_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN); @Override @@ -67,13 +29,12 @@ public int compare(final byte[] left, final byte[] right) { final int strideLimit = minLength & -stride; int i; - /* - * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower - * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. - */ + ByteBuffer leftBuffer = ByteBuffer.wrap(left).order(ByteOrder.nativeOrder()); + ByteBuffer rightBuffer = ByteBuffer.wrap(right).order(ByteOrder.nativeOrder()); + for (i = 0; i < strideLimit; i += stride) { - final long lw = theUnsafe.getLong(left, BYTE_ARRAY_BASE_OFFSET + (long) i); - final long rw = theUnsafe.getLong(right, BYTE_ARRAY_BASE_OFFSET + (long) i); + final long lw = leftBuffer.getLong(i); + final long rw = rightBuffer.getLong(i); if (lw != rw) { if (BIG_ENDIAN) { return unsignedLongsCompare(lw, rw); @@ -83,7 +44,6 @@ public int compare(final byte[] left, final byte[] right) { } } - // The epilogue to cover the last (minLength % stride) elements. for (; i < minLength; i++) { final int result = UnsignedBytesComparator.compare(left[i], right[i]); if (result != 0) @@ -99,18 +59,16 @@ public boolean equals(final byte[] left, final byte[] right, final int length) { final int strideLimit = length & -stride; int i; - /* - * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower - * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. - */ + ByteBuffer leftBuffer = ByteBuffer.wrap(left).order(ByteOrder.nativeOrder()); + ByteBuffer rightBuffer = ByteBuffer.wrap(right).order(ByteOrder.nativeOrder()); + for (i = 0; i < strideLimit; i += stride) { - final long lw = theUnsafe.getLong(left, BYTE_ARRAY_BASE_OFFSET + (long) i); - final long rw = theUnsafe.getLong(right, BYTE_ARRAY_BASE_OFFSET + (long) i); + final long lw = leftBuffer.getLong(i); + final long rw = rightBuffer.getLong(i); if (lw != rw) return false; } - // The epilogue to cover the last (minLength % stride) elements. for (; i < length; i++) { final int result = UnsignedBytesComparator.compare(left[i], right[i]); if (result != 0) @@ -122,7 +80,7 @@ public boolean equals(final byte[] left, final byte[] right, final int length) { @Override public String toString() { - return "UnsignedBytes.lexicographicalComparator() (sun.misc.Unsafe version)"; + return "UnsignedBytes.lexicographicalComparator() (ByteBuffer version)"; } } @@ -141,7 +99,6 @@ public int compare(final byte[] left, final byte[] right) { @Override public boolean equals(final byte[] left, final byte[] right, final int length) { - // OPTIMIZATION: TEST LAST BYTE FIRST int result = UnsignedBytesComparator.compare(left[length - 1], right[length - 1]); if (result != 0) return false; @@ -169,34 +126,4 @@ public static int unsignedLongsCompare(final long a, final long b) { final long b2 = b ^ Long.MIN_VALUE; return Long.compare(a2, b2); } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. Replace with a simple - * call to Unsafe.getUnsafe when integrating into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static Unsafe getUnsafe() { - try { - return Unsafe.getUnsafe(); - } catch (final SecurityException e) { - // that's okay; try reflection instead - } - try { - return AccessController.doPrivileged((PrivilegedExceptionAction) () -> { - final Class k = Unsafe.class; - for (final Field f : k.getDeclaredFields()) { - f.setAccessible(true); - final Object x = f.get(null); - if (k.isInstance(x)) { - return k.cast(x); - } - } - throw new NoSuchFieldError("the Unsafe"); - }); - } catch (final PrivilegedActionException e) { - throw new ArcadeDBException("Could not initialize intrinsics", e.getCause()); - } - } - } From 5ea85c7e8d45d774fc652b51dbcd0be6b931d79a Mon Sep 17 00:00:00 2001 From: Roberto Franchini Date: Sun, 11 May 2025 18:30:36 +0200 Subject: [PATCH 2/3] refactor: replace sun.misc.Unsafe with java.lang.foreign.MemorySegment in comparators --- .../arcadedb/serializer/BinaryComparator.java | 14 +-- .../arcadedb/serializer/BinarySerializer.java | 22 +++-- .../serializer/UnsignedBytesComparator.java | 94 ++++++++++++++----- 3 files changed, 95 insertions(+), 35 deletions(-) diff --git a/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java b/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java index 2aabc5da27..36046d737a 100644 --- a/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java +++ b/engine/src/main/java/com/arcadedb/serializer/BinaryComparator.java @@ -25,9 +25,11 @@ import com.arcadedb.utility.CollectionUtils; import com.arcadedb.utility.DateUtils; -import java.math.*; -import java.time.temporal.*; -import java.util.*; +import java.math.BigDecimal; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +import java.util.List; +import java.util.Map; public class BinaryComparator { public int compare(final Object value1, final byte type1, final Object value2, final byte type2) { @@ -329,8 +331,7 @@ public int compare(final Object value1, final byte type1, final Object value2, f } case BinaryTypes.TYPE_LIST: { - switch (type2) { - case BinaryTypes.TYPE_LIST: + if (type2 == BinaryTypes.TYPE_LIST) { final List v1 = value1.getClass().isArray() ? Arrays.asList((Object[]) value1) : (List) value1; final List v2 = value2.getClass().isArray() ? Arrays.asList((Object[]) value2) : (List) value2; @@ -340,8 +341,7 @@ public int compare(final Object value1, final byte type1, final Object value2, f } case BinaryTypes.TYPE_MAP: { - switch (type2) { - case BinaryTypes.TYPE_MAP: + if (type2 == BinaryTypes.TYPE_MAP) { return CollectionUtils.compare((Map) value1, (Map) value2); } break; diff --git a/engine/src/main/java/com/arcadedb/serializer/BinarySerializer.java b/engine/src/main/java/com/arcadedb/serializer/BinarySerializer.java index 104be15c57..d232861e04 100644 --- a/engine/src/main/java/com/arcadedb/serializer/BinarySerializer.java +++ b/engine/src/main/java/com/arcadedb/serializer/BinarySerializer.java @@ -49,12 +49,22 @@ import com.arcadedb.serializer.json.JSONObject; import com.arcadedb.utility.DateUtils; -import java.lang.reflect.*; -import java.math.*; -import java.time.*; -import java.time.temporal.*; -import java.util.*; -import java.util.logging.*; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.logging.Level; /** * Default serializer implementation. diff --git a/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java b/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java index 2c0b1ad816..e72f62dea9 100644 --- a/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java +++ b/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java @@ -1,25 +1,57 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: 2021-present Arcade Data Ltd (info@arcadedata.com) + * SPDX-License-Identifier: Apache-2.0 + */ package com.arcadedb.serializer; -import java.nio.ByteBuffer; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.nio.ByteOrder; /** * This class was inspired by Guava's UnsignedBytes, under Apache 2 license. + * Updated to use java.lang.foreign.MemorySegment instead of sun.misc.Unsafe + * according to JEP 471 recommendations. * * @author Louis Wasserman * @author Brian Milch * @author Colin Evans * @author Luca Garulli (l.garulli@arcadedata.com) */ + public final class UnsignedBytesComparator { private static final int UNSIGNED_MASK = 0xFF; public static final PureJavaComparator PURE_JAVA_COMPARATOR = new PureJavaComparator(); - public static final ByteArrayComparator BEST_COMPARATOR = new ByteBufferComparator(); + public static final ByteArrayComparator BEST_COMPARATOR; + + static { + // Fall back to the pure Java implementation unless we're in a 64-bit JVM + // Note: We don't need to check the offset alignment as with MemorySegment API + if (!"64".equals(System.getProperty("sun.arch.data.model"))) { + BEST_COMPARATOR = PURE_JAVA_COMPARATOR; // force fallback to PureJavaComparator + } else { + BEST_COMPARATOR = new ModernComparator(); + } + } private UnsignedBytesComparator() { } - public static class ByteBufferComparator implements ByteArrayComparator { + public static class ModernComparator implements ByteArrayComparator { static final boolean BIG_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN); @Override @@ -29,21 +61,30 @@ public int compare(final byte[] left, final byte[] right) { final int strideLimit = minLength & -stride; int i; - ByteBuffer leftBuffer = ByteBuffer.wrap(left).order(ByteOrder.nativeOrder()); - ByteBuffer rightBuffer = ByteBuffer.wrap(right).order(ByteOrder.nativeOrder()); - - for (i = 0; i < strideLimit; i += stride) { - final long lw = leftBuffer.getLong(i); - final long rw = rightBuffer.getLong(i); - if (lw != rw) { - if (BIG_ENDIAN) { - return unsignedLongsCompare(lw, rw); + try (Arena arena = Arena.ofConfined()) { + MemorySegment leftSegment = MemorySegment.ofArray(left); + MemorySegment rightSegment = MemorySegment.ofArray(right); + + /* + * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower + * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. + */ + for (i = 0; i < strideLimit; i += stride) { + final long lw = leftSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); + final long rw = rightSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); + + if (lw != rw) { + if (BIG_ENDIAN) { + return unsignedLongsCompare(lw, rw); + } + + final int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7; + return ((int) ((lw >>> n) & UNSIGNED_MASK)) - ((int) ((rw >>> n) & UNSIGNED_MASK)); } - final int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7; - return ((int) ((lw >>> n) & UNSIGNED_MASK)) - ((int) ((rw >>> n) & UNSIGNED_MASK)); } } + // The epilogue to cover the last (minLength % stride) elements. for (; i < minLength; i++) { final int result = UnsignedBytesComparator.compare(left[i], right[i]); if (result != 0) @@ -59,16 +100,24 @@ public boolean equals(final byte[] left, final byte[] right, final int length) { final int strideLimit = length & -stride; int i; - ByteBuffer leftBuffer = ByteBuffer.wrap(left).order(ByteOrder.nativeOrder()); - ByteBuffer rightBuffer = ByteBuffer.wrap(right).order(ByteOrder.nativeOrder()); + try (Arena arena = Arena.ofConfined()) { + MemorySegment leftSegment = MemorySegment.ofArray(left); + MemorySegment rightSegment = MemorySegment.ofArray(right); - for (i = 0; i < strideLimit; i += stride) { - final long lw = leftBuffer.getLong(i); - final long rw = rightBuffer.getLong(i); - if (lw != rw) - return false; + /* + * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower + * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. + */ + for (i = 0; i < strideLimit; i += stride) { + final long lw = leftSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); + final long rw = rightSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); + + if (lw != rw) + return false; + } } + // The epilogue to cover the last (minLength % stride) elements. for (; i < length; i++) { final int result = UnsignedBytesComparator.compare(left[i], right[i]); if (result != 0) @@ -80,7 +129,7 @@ public boolean equals(final byte[] left, final byte[] right, final int length) { @Override public String toString() { - return "UnsignedBytes.lexicographicalComparator() (ByteBuffer version)"; + return "UnsignedBytes.lexicographicalComparator() (java.lang.foreign.MemorySegment version)"; } } @@ -99,6 +148,7 @@ public int compare(final byte[] left, final byte[] right) { @Override public boolean equals(final byte[] left, final byte[] right, final int length) { + // OPTIMIZATION: TEST LAST BYTE FIRST int result = UnsignedBytesComparator.compare(left[length - 1], right[length - 1]); if (result != 0) return false; From fb4c355d440591fc97e0429928e240aed2c5e90a Mon Sep 17 00:00:00 2001 From: Roberto Franchini Date: Fri, 10 Oct 2025 21:17:43 +0200 Subject: [PATCH 3/3] refactor: simplify UnsignedBytesComparator by removing unnecessary use of Arena and optimizing comparison logic --- .../serializer/UnsignedBytesComparator.java | 82 ++++--------------- 1 file changed, 18 insertions(+), 64 deletions(-) diff --git a/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java b/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java index e72f62dea9..659511233c 100644 --- a/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java +++ b/engine/src/main/java/com/arcadedb/serializer/UnsignedBytesComparator.java @@ -17,10 +17,7 @@ */ package com.arcadedb.serializer; -import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.nio.ByteOrder; /** * This class was inspired by Guava's UnsignedBytes, under Apache 2 license. @@ -52,79 +49,36 @@ private UnsignedBytesComparator() { } public static class ModernComparator implements ByteArrayComparator { - static final boolean BIG_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.BIG_ENDIAN); - @Override public int compare(final byte[] left, final byte[] right) { - final int stride = 8; - final int minLength = Math.min(left.length, right.length); - final int strideLimit = minLength & -stride; - int i; - - try (Arena arena = Arena.ofConfined()) { - MemorySegment leftSegment = MemorySegment.ofArray(left); - MemorySegment rightSegment = MemorySegment.ofArray(right); - - /* - * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower - * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. - */ - for (i = 0; i < strideLimit; i += stride) { - final long lw = leftSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); - final long rw = rightSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); - - if (lw != rw) { - if (BIG_ENDIAN) { - return unsignedLongsCompare(lw, rw); - } - - final int n = Long.numberOfTrailingZeros(lw ^ rw) & ~0x7; - return ((int) ((lw >>> n) & UNSIGNED_MASK)) - ((int) ((rw >>> n) & UNSIGNED_MASK)); - } - } + // assumes left and right are non-null + final MemorySegment leftSegment = MemorySegment.ofArray(left); + final MemorySegment rightSegment = MemorySegment.ofArray(right); + + final long index = leftSegment.mismatch(rightSegment); + if (index == -1) { + return Integer.compare(left.length, right.length); } - // The epilogue to cover the last (minLength % stride) elements. - for (; i < minLength; i++) { - final int result = UnsignedBytesComparator.compare(left[i], right[i]); - if (result != 0) - return result; + // index is either the byte offset which differs or the length of the shorter array + if (index >= left.length || index >= right.length) { + return Integer.compare(left.length, right.length); } - return left.length - right.length; + return Integer.compare(Byte.toUnsignedInt(left[(int) index]), Byte.toUnsignedInt(right[(int) index])); } @Override public boolean equals(final byte[] left, final byte[] right, final int length) { - final int stride = 8; - final int strideLimit = length & -stride; - int i; - - try (Arena arena = Arena.ofConfined()) { - MemorySegment leftSegment = MemorySegment.ofArray(left); - MemorySegment rightSegment = MemorySegment.ofArray(right); - - /* - * Compare 8 bytes at a time. Benchmarking on x86 shows a stride of 8 bytes is no slower - * than 4 bytes even on 32-bit. On the other hand, it is substantially faster on 64-bit. - */ - for (i = 0; i < strideLimit; i += stride) { - final long lw = leftSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); - final long rw = rightSegment.get(ValueLayout.JAVA_LONG_UNALIGNED, i); - - if (lw != rw) - return false; - } - } + // assumes left and right are non-null and length is non-zero + if (left.length < length || right.length < length) + return false; - // The epilogue to cover the last (minLength % stride) elements. - for (; i < length; i++) { - final int result = UnsignedBytesComparator.compare(left[i], right[i]); - if (result != 0) - return false; - } + final MemorySegment leftSegment = MemorySegment.ofArray(left).asSlice(0, length); + final MemorySegment rightSegment = MemorySegment.ofArray(right).asSlice(0, length); - return true; + // mismatch is optimized and should be faster than a for loop + return leftSegment.mismatch(rightSegment) == -1; } @Override