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]

Reply via email to