This is an automated email from the ASF dual-hosted git repository.
fanningpj pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/pekko.git
The following commit(s) were added to refs/heads/main by this push:
new 16de1600e3 use swar methods in a few more places (#2839)
16de1600e3 is described below
commit 16de1600e3101c2cd5d24aeb5574797adbd3e889
Author: PJ Fanning <[email protected]>
AuthorDate: Mon Apr 6 09:42:14 2026 +0200
use swar methods in a few more places (#2839)
* use swar in more places
Update SWARUtil.scala
scalafmt
Update SnapshotSerializer.scala
Update SWARUtil.scala
Update SWARUtil.scala
should be little endian
* Create SWARUtilSpec.scala
* more tests
* Update SWARUtilSpec.scala
---
.../scala/org/apache/pekko/util/SWARUtilSpec.scala | 54 +++++++++
.../scala/org/apache/pekko/util/SWARUtil.scala | 131 +++++++++++++++++++--
.../serialization/SnapshotSerializer.scala | 24 +---
.../remote/artery/compress/CountMinSketch.java | 7 +-
4 files changed, 184 insertions(+), 32 deletions(-)
diff --git
a/actor-tests/src/test/scala/org/apache/pekko/util/SWARUtilSpec.scala
b/actor-tests/src/test/scala/org/apache/pekko/util/SWARUtilSpec.scala
new file mode 100644
index 0000000000..b660e37406
--- /dev/null
+++ b/actor-tests/src/test/scala/org/apache/pekko/util/SWARUtilSpec.scala
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.pekko.util
+
+import org.scalatest.wordspec.AnyWordSpec
+import org.scalatest.matchers.should.Matchers
+
+class SWARUtilSpec extends AnyWordSpec with Matchers {
+
+ val testData = Array[Byte](0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15)
+
+ "SWARUtil" must {
+ "getLong" in {
+ SWARUtil.getLong(testData, 0) should ===(0x0001020304050607L)
+ SWARUtil.getLong(testData, 0, true) should ===(0x0001020304050607L)
+ SWARUtil.getLong(testData, 0, false) should ===(0x0706050403020100L)
+ SWARUtil.getLongBEWithoutMethodHandle(testData, 0) should
===(0x0001020304050607L)
+ SWARUtil.getLongLEWithoutMethodHandle(testData, 0) should
===(0x0706050403020100L)
+ SWARUtil.getLong(testData, 8) should ===(0x08090A0B0C0D0E0FL)
+ SWARUtil.getLong(testData, 8, true) should ===(0x08090A0B0C0D0E0FL)
+ SWARUtil.getLong(testData, 8, false) should ===(0x0F0E0D0C0B0A0908L)
+ SWARUtil.getLongBEWithoutMethodHandle(testData, 8) should
===(0x08090A0B0C0D0E0FL)
+ SWARUtil.getLongLEWithoutMethodHandle(testData, 8) should
===(0x0F0E0D0C0B0A0908L)
+ }
+ "getInt" in {
+ SWARUtil.getInt(testData, 0) should ===(0x00010203)
+ SWARUtil.getInt(testData, 0, true) should ===(0x00010203)
+ SWARUtil.getInt(testData, 0, false) should ===(0x03020100)
+ SWARUtil.getIntBEWithoutMethodHandle(testData, 0) should ===(0x00010203)
+ SWARUtil.getIntLEWithoutMethodHandle(testData, 0) should ===(0x03020100)
+ SWARUtil.getInt(testData, 4) should ===(0x04050607)
+ SWARUtil.getInt(testData, 4, true) should ===(0x04050607)
+ SWARUtil.getInt(testData, 4, false) should ===(0x07060504)
+ SWARUtil.getIntBEWithoutMethodHandle(testData, 4) should ===(0x04050607)
+ SWARUtil.getIntLEWithoutMethodHandle(testData, 4) should ===(0x07060504)
+ }
+ }
+
+}
diff --git a/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala
b/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala
index cabbb52c1c..bbc8fc22c8 100644
--- a/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala
+++ b/actor/src/main/scala/org/apache/pekko/util/SWARUtil.scala
@@ -27,7 +27,7 @@ import org.apache.pekko.annotation.InternalApi
* </p>
*/
@InternalApi
-private[util] object SWARUtil {
+private[pekko] object SWARUtil {
private val (longBeArrayView, longBeArrayViewSupported) =
try {
@@ -38,6 +38,33 @@ private[util] object SWARUtil {
case _: Throwable => (null, false)
}
+ private val (longLeArrayView, longLeArrayViewSupported) =
+ try {
+ (MethodHandles.byteArrayViewVarHandle(
+ classOf[Array[Long]], java.nio.ByteOrder.LITTLE_ENDIAN),
+ true)
+ } catch {
+ case _: Throwable => (null, false)
+ }
+
+ private val (intBeArrayView, intBeArrayViewSupported) =
+ try {
+ (MethodHandles.byteArrayViewVarHandle(
+ classOf[Array[Int]], java.nio.ByteOrder.BIG_ENDIAN),
+ true)
+ } catch {
+ case _: Throwable => (null, false)
+ }
+
+ private val (intLeArrayView, intLeArrayViewSupported) =
+ try {
+ (MethodHandles.byteArrayViewVarHandle(
+ classOf[Array[Int]], java.nio.ByteOrder.LITTLE_ENDIAN),
+ true)
+ } catch {
+ case _: Throwable => (null, false)
+ }
+
/**
* Compiles given byte into a long pattern suitable for SWAR operations.
*/
@@ -81,14 +108,100 @@ private[util] object SWARUtil {
if (longBeArrayViewSupported) {
longBeArrayView.get(array, index)
} else {
- (array(index).toLong & 0xFF) << 56 |
- (array(index + 1).toLong & 0xFF) << 48 |
- (array(index + 2).toLong & 0xFF) << 40 |
- (array(index + 3).toLong & 0xFF) << 32 |
- (array(index + 4).toLong & 0xFF) << 24 |
- (array(index + 5).toLong & 0xFF) << 16 |
- (array(index + 6).toLong & 0xFF) << 8 |
- (array(index + 7).toLong & 0xFF)
+ getLongBEWithoutMethodHandle(array, index)
}
}
+
+ /**
+ * Returns the long value at the specified index in the given byte array.
+ * Uses a VarHandle byte array view if supported.
+ * Does not range check - assumes caller has checked bounds.
+ *
+ * @param array the byte array to read from
+ * @param index the index to read from
+ * @return the long value at the specified index
+ */
+ def getLong(array: Array[Byte], index: Int, bigEndian: Boolean): Long = {
+ if (bigEndian) {
+ getLong(array, index)
+ } else if (longLeArrayViewSupported) {
+ longLeArrayView.get(array, index)
+ } else {
+ getLongLEWithoutMethodHandle(array, index)
+ }
+ }
+
+ /**
+ * Returns the int value at the specified index in the given byte array.
+ * Uses big-endian byte order. Uses a VarHandle byte array view if supported.
+ * Does not range check - assumes caller has checked bounds.
+ *
+ * @param array the byte array to read from
+ * @param index the index to read from
+ * @return the int value at the specified index
+ */
+ def getInt(array: Array[Byte], index: Int): Int = {
+ if (intBeArrayViewSupported) {
+ intBeArrayView.get(array, index)
+ } else {
+ getIntBEWithoutMethodHandle(array, index)
+ }
+ }
+
+ /**
+ * Returns the int value at the specified index in the given byte array.
+ * Uses a VarHandle byte array view if supported.
+ * Does not range check - assumes caller has checked bounds.
+ *
+ * @param array the byte array to read from
+ * @param index the index to read from
+ * @param bigEndian whether to use big-endian or little-endian byte order
+ * @return the int value at the specified index
+ */
+ def getInt(array: Array[Byte], index: Int, bigEndian: Boolean): Int = {
+ if (bigEndian) {
+ getInt(array, index)
+ } else if (intLeArrayViewSupported) {
+ intLeArrayView.get(array, index)
+ } else {
+ getIntLEWithoutMethodHandle(array, index)
+ }
+ }
+
+ private[pekko] def getLongBEWithoutMethodHandle(array: Array[Byte], index:
Int): Long = {
+ (array(index).toLong & 0xFF) << 56 |
+ (array(index + 1).toLong & 0xFF) << 48 |
+ (array(index + 2).toLong & 0xFF) << 40 |
+ (array(index + 3).toLong & 0xFF) << 32 |
+ (array(index + 4).toLong & 0xFF) << 24 |
+ (array(index + 5).toLong & 0xFF) << 16 |
+ (array(index + 6).toLong & 0xFF) << 8 |
+ (array(index + 7).toLong & 0xFF)
+ }
+
+ private[pekko] def getLongLEWithoutMethodHandle(array: Array[Byte], index:
Int): Long = {
+ (array(index).toLong & 0xFF) |
+ (array(index + 1).toLong & 0xFF) << 8 |
+ (array(index + 2).toLong & 0xFF) << 16 |
+ (array(index + 3).toLong & 0xFF) << 24 |
+ (array(index + 4).toLong & 0xFF) << 32 |
+ (array(index + 5).toLong & 0xFF) << 40 |
+ (array(index + 6).toLong & 0xFF) << 48 |
+ (array(index + 7).toLong & 0xFF) << 56
+ }
+
+ private[pekko] def getIntBEWithoutMethodHandle(array: Array[Byte], index:
Int): Int = {
+ (array(index) & 0xFF) << 24 |
+ (array(index + 1) & 0xFF) << 16 |
+ (array(index + 2) & 0xFF) << 8 |
+ (array(index + 3) & 0xFF)
+ }
+
+ private[pekko] def getIntLEWithoutMethodHandle(array: Array[Byte], index:
Int): Int = {
+ (array(index) & 0xFF) |
+ (array(index + 1) & 0xFF) << 8 |
+ (array(index + 2) & 0xFF) << 16 |
+ (array(index + 3) & 0xFF) << 24
+ }
+
}
diff --git
a/persistence/src/main/scala/org/apache/pekko/persistence/serialization/SnapshotSerializer.scala
b/persistence/src/main/scala/org/apache/pekko/persistence/serialization/SnapshotSerializer.scala
index 5270369cf2..44c354ddbc 100644
---
a/persistence/src/main/scala/org/apache/pekko/persistence/serialization/SnapshotSerializer.scala
+++
b/persistence/src/main/scala/org/apache/pekko/persistence/serialization/SnapshotSerializer.scala
@@ -17,9 +17,9 @@ import java.io._
import org.apache.pekko
import pekko.actor._
-import pekko.io.UnsynchronizedByteArrayInputStream
import pekko.serialization._
import pekko.util.ByteString.UTF_8
+import pekko.util.SWARUtil
/**
* Wrapper for snapshot `data`. Snapshot `data` are the actual snapshot
objects captured by
@@ -90,18 +90,18 @@ class SnapshotSerializer(val system: ExtendedActorSystem)
extends BaseSerializer
}
private def headerFromBinary(bytes: Array[Byte]): (Int, String) = {
- val in = new UnsynchronizedByteArrayInputStream(bytes)
- val serializerId = readInt(in)
+ if (bytes.length < 4) throw new IllegalArgumentException("Invalid snapshot
header, too short")
+ val serializerId = SWARUtil.getInt(bytes, 0, false)
if ((serializerId & 0xEDAC) == 0xEDAC) // Java Serialization magic value
throw new NotSerializableException(s"Replaying snapshot from akka 2.3.x
version is not supported any more")
- val remaining = in.available
+ val remaining = bytes.length - 4
val manifest =
if (remaining == 0) ""
else {
val manifestBytes = new Array[Byte](remaining)
- in.read(manifestBytes)
+ System.arraycopy(bytes, 4, manifestBytes, 0, remaining)
migrateManifestToPekkoIfNecessary(new String(manifestBytes, UTF_8))
}
(serializerId, manifest)
@@ -164,7 +164,7 @@ class SnapshotSerializer(val system: ExtendedActorSystem)
extends BaseSerializer
}
private def snapshotFromBinary(bytes: Array[Byte]): AnyRef = {
- val headerLength = readInt(new UnsynchronizedByteArrayInputStream(bytes))
+ val headerLength = SWARUtil.getInt(bytes, 0, false)
val headerBytes = bytes.slice(4, headerLength + 4)
val snapshotBytes = bytes.drop(headerLength + 4)
@@ -181,16 +181,4 @@ class SnapshotSerializer(val system: ExtendedActorSystem)
extends BaseSerializer
out.write(i >>> 16)
out.write(i >>> 24)
}
-
- private def readInt(in: InputStream): Int = {
- val b1 = in.read
- val b2 = in.read
- val b3 = in.read
- val b4 = in.read
-
- if ((b1 | b2 | b3 | b3) == -1) throw new EOFException
-
- (b4 << 24) | (b3 << 16) | (b2 << 8) | b1
- }
-
}
diff --git
a/remote/src/main/java/org/apache/pekko/remote/artery/compress/CountMinSketch.java
b/remote/src/main/java/org/apache/pekko/remote/artery/compress/CountMinSketch.java
index 997ab9f468..222ab2b925 100644
---
a/remote/src/main/java/org/apache/pekko/remote/artery/compress/CountMinSketch.java
+++
b/remote/src/main/java/org/apache/pekko/remote/artery/compress/CountMinSketch.java
@@ -14,6 +14,7 @@
package org.apache.pekko.remote.artery.compress;
import org.apache.pekko.actor.ActorRef;
+import org.apache.pekko.util.SWARUtil;
/**
* INTERNAL API: Count-Min Sketch datastructure.
@@ -198,11 +199,7 @@ public class CountMinSketch {
// Body
int i = 0;
while (len >= 4) {
- int k = data[i] & 0xFF;
- k |= (data[i + 1] & 0xFF) << 8;
- k |= (data[i + 2] & 0xFF) << 16;
- k |= (data[i + 3] & 0xFF) << 24;
-
+ int k = SWARUtil.getInt(data, i, false);
h = mix(h, k);
i += 4;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]