diff --git a/akka-actor-tests/src/test/scala/akka/util/AsciiStringCopySpec.scala b/akka-actor-tests/src/test/scala/akka/util/AsciiStringCopySpec.scala index eb6fb46996..7c9bf95d9c 100644 --- a/akka-actor-tests/src/test/scala/akka/util/AsciiStringCopySpec.scala +++ b/akka-actor-tests/src/test/scala/akka/util/AsciiStringCopySpec.scala @@ -12,6 +12,20 @@ class AsciiStringCopySpec extends WordSpec with Matchers { "The copyUSAsciiStrToBytes optimization" must { + "select working algorithm" in { + if (Unsafe.isIsJavaVersion9Plus) { + Unsafe.testUSAsciiStrToBytesAlgorithm0("abc") should ===(true) + // this is known to fail with JDK 11 on ARM32 (Raspberry Pi), + // and therefore algorithm 0 is selected on that architecture + Unsafe.testUSAsciiStrToBytesAlgorithm1("abc") should ===(true) + Unsafe.testUSAsciiStrToBytesAlgorithm2("abc") should ===(false) + } else { + Unsafe.testUSAsciiStrToBytesAlgorithm0("abc") should ===(true) + Unsafe.testUSAsciiStrToBytesAlgorithm1("abc") should ===(false) + Unsafe.testUSAsciiStrToBytesAlgorithm2("abc") should ===(true) + } + } + "copy string internal representation successfully" in { val ascii = "abcdefghijklmnopqrstuvxyz" val byteArray = new Array[Byte](ascii.length) diff --git a/akka-actor/src/main/scala/akka/util/Unsafe.java b/akka-actor/src/main/scala/akka/util/Unsafe.java index c23915e83b..3662e8d85b 100644 --- a/akka-actor/src/main/scala/akka/util/Unsafe.java +++ b/akka-actor/src/main/scala/akka/util/Unsafe.java @@ -4,17 +4,22 @@ package akka.util; +import akka.annotation.InternalApi; + import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; import java.util.Arrays; /** * INTERNAL API */ +@InternalApi public final class Unsafe { public static final sun.misc.Unsafe instance; private static final long stringValueFieldOffset; private static final boolean isJavaVersion9Plus; + private static final int copyUSAsciiStrToBytesAlgorithm; static { try { @@ -30,29 +35,110 @@ public final class Unsafe { else instance = found; stringValueFieldOffset = instance.objectFieldOffset(String.class.getDeclaredField("value")); - // See Oracle section 1.5.3 at: - // https://docs.oracle.com/javase/8/docs/technotes/guides/versioning/spec/versioning2.html - final int[] version = Arrays. - stream(System.getProperty("java.specification.version").split("\\.")). - mapToInt(Integer::parseInt). - toArray(); - final int javaVersion = version[0] == 1 ? version[1] : version[0]; - isJavaVersion9Plus = javaVersion > 8; + isJavaVersion9Plus = isIsJavaVersion9Plus(); + + // Select optimization algorithm for `copyUSAciiBytesToStr`. + // For example algorithm 1 will fail with JDK 11 on ARM32 (Raspberry Pi), + // and therefore algorithm 0 is selected on that architecture. + String testStr = "abc"; + if (isJavaVersion9Plus && testUSAsciiStrToBytesAlgorithm1(testStr)) + copyUSAsciiStrToBytesAlgorithm = 1; + else if (testUSAsciiStrToBytesAlgorithm2(testStr)) + copyUSAsciiStrToBytesAlgorithm = 2; + else + copyUSAsciiStrToBytesAlgorithm = 0; + } catch (Throwable t) { throw new ExceptionInInitializerError(t); } } - public static void copyUSAsciiStrToBytes(String str, byte[] bytes) { - if (isJavaVersion9Plus) { + static boolean isIsJavaVersion9Plus() { + // See Oracle section 1.5.3 at: + // https://docs.oracle.com/javase/8/docs/technotes/guides/versioning/spec/versioning2.html + final int[] version = Arrays. + stream(System.getProperty("java.specification.version").split("\\.")). + mapToInt(Integer::parseInt). + toArray(); + final int javaVersion = version[0] == 1 ? version[1] : version[0]; + return javaVersion > 8; + } + + static boolean testUSAsciiStrToBytesAlgorithm0(String str) { + try { + byte[] bytes = new byte[str.length()]; + + // copy of implementation in copyUSAciiBytesToStr + byte[] strBytes = str.getBytes(StandardCharsets.US_ASCII); + System.arraycopy(strBytes, 0, bytes, 0, str.length()); + // end copy + + String result = copyUSAciiBytesToStr(str.length(), bytes); + return str.equals(result); + } catch (Throwable all) { + return false; + } + } + + static boolean testUSAsciiStrToBytesAlgorithm1(String str) { + try { + byte[] bytes = new byte[str.length()]; + + // copy of implementation in copyUSAciiBytesToStr final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset); System.arraycopy(chars, 0, bytes, 0, str.length()); - } else { + // end copy + + String result = copyUSAciiBytesToStr(str.length(), bytes); + return str.equals(result); + } catch (Throwable all) { + return false; + } + } + + static boolean testUSAsciiStrToBytesAlgorithm2(String str) { + try { + byte[] bytes = new byte[str.length()]; + + // copy of implementation in copyUSAciiBytesToStr final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset); int i = 0; while (i < str.length()) { bytes[i] = (byte) chars[i++]; } + // end copy + + String result = copyUSAciiBytesToStr(str.length(), bytes); + return str.equals(result); + } catch (Throwable all) { + return false; + } + } + + private static String copyUSAciiBytesToStr(int length, byte[] bytes) { + char[] resultChars = new char[length]; + int i = 0; + while (i < length) { + // UsAscii + resultChars[i] = (char) bytes[i]; + i += 1; + } + return String.valueOf(resultChars, 0, length); + } + + public static void copyUSAsciiStrToBytes(String str, byte[] bytes) { + if (copyUSAsciiStrToBytesAlgorithm == 1) { + final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset); + System.arraycopy(chars, 0, bytes, 0, str.length()); + } else if (copyUSAsciiStrToBytesAlgorithm == 2) { + final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset); + int i = 0; + while (i < str.length()) { + bytes[i] = (byte) chars[i++]; + } + } else { + byte[] strBytes = str.getBytes(StandardCharsets.US_ASCII); + System.arraycopy(strBytes, 0, bytes, 0, str.length()); } } @@ -61,7 +147,7 @@ public final class Unsafe { long s1 = 601258; int i = 0; - if (isJavaVersion9Plus) { + if (copyUSAsciiStrToBytesAlgorithm == 1) { final byte[] chars = (byte[]) instance.getObject(str, stringValueFieldOffset); while (i < str.length()) { long x = s0 ^ (long)chars[i++]; // Mix character into PRNG state @@ -74,12 +160,25 @@ public final class Unsafe { x ^= x >>> 17; s1 = x ^ y; } - } else { + } else if (copyUSAsciiStrToBytesAlgorithm == 2) { final char[] chars = (char[]) instance.getObject(str, stringValueFieldOffset); while (i < str.length()) { long x = s0 ^ (long)chars[i++]; // Mix character into PRNG state long y = s1; + // Xorshift128+ round + s0 = y; + x ^= x << 23; + y ^= y >>> 26; + x ^= x >>> 17; + s1 = x ^ y; + } + } else { + byte[] chars = str.getBytes(StandardCharsets.US_ASCII); + while (i < str.length()) { + long x = s0 ^ (long)chars[i++]; // Mix character into PRNG state + long y = s1; + // Xorshift128+ round s0 = y; x ^= x << 23;