فهرست منبع

Use VarHandles for number conversions (#80367)

Convert longs, ints and shorts back and from bytes, using VarHandles instead 
of using series of bit shifts. The internal JDK var handles approach allows the 
JVM to perform the conversion operations by using direct memory loads.

Relates to #78823
Nikola Grcevski 4 سال پیش
والد
کامیت
9c659bd72a

+ 15 - 20
server/src/main/java/org/elasticsearch/common/Numbers.java

@@ -10,32 +10,38 @@ package org.elasticsearch.common;
 
 import org.apache.lucene.util.BytesRef;
 
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.nio.ByteOrder;
 
 /**
  * A set of utilities for numbers.
  */
 public final class Numbers {
 
+    private static final VarHandle BIG_ENDIAN_SHORT = MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.BIG_ENDIAN);
+
+    private static final VarHandle BIG_ENDIAN_INT = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN);
+
+    private static final VarHandle BIG_ENDIAN_LONG = MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.BIG_ENDIAN);
+
     private static final BigInteger MAX_LONG_VALUE = BigInteger.valueOf(Long.MAX_VALUE);
     private static final BigInteger MIN_LONG_VALUE = BigInteger.valueOf(Long.MIN_VALUE);
 
     private Numbers() {}
 
     public static short bytesToShort(byte[] bytes, int offset) {
-        return (short) (((bytes[offset] & 0xFF) << 8) | (bytes[offset + 1] & 0xFF));
+        return (short) BIG_ENDIAN_SHORT.get(bytes, offset);
     }
 
     public static int bytesToInt(byte[] bytes, int offset) {
-        return ((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8) | (bytes[offset + 3]
-            & 0xFF);
+        return (int) BIG_ENDIAN_INT.get(bytes, offset);
     }
 
     public static long bytesToLong(byte[] bytes, int offset) {
-        return (((long) (((bytes[offset] & 0xFF) << 24) | ((bytes[offset + 1] & 0xFF) << 16) | ((bytes[offset + 2] & 0xFF) << 8)
-            | (bytes[offset + 3] & 0xFF))) << 32) | ((((bytes[offset + 4] & 0xFF) << 24) | ((bytes[offset + 5] & 0xFF) << 16)
-                | ((bytes[offset + 6] & 0xFF) << 8) | (bytes[offset + 7] & 0xFF)) & 0xFFFFFFFFL);
+        return (long) BIG_ENDIAN_LONG.get(bytes, offset);
     }
 
     public static long bytesToLong(BytesRef bytes) {
@@ -44,10 +50,7 @@ public final class Numbers {
 
     public static byte[] intToBytes(int val) {
         byte[] arr = new byte[4];
-        arr[0] = (byte) (val >>> 24);
-        arr[1] = (byte) (val >>> 16);
-        arr[2] = (byte) (val >>> 8);
-        arr[3] = (byte) (val);
+        BIG_ENDIAN_INT.set(arr, 0, val);
         return arr;
     }
 
@@ -59,8 +62,7 @@ public final class Numbers {
      */
     public static byte[] shortToBytes(int val) {
         byte[] arr = new byte[2];
-        arr[0] = (byte) (val >>> 8);
-        arr[1] = (byte) (val);
+        BIG_ENDIAN_SHORT.set(arr, 0, (short) val);
         return arr;
     }
 
@@ -72,14 +74,7 @@ public final class Numbers {
      */
     public static byte[] longToBytes(long val) {
         byte[] arr = new byte[8];
-        arr[0] = (byte) (val >>> 56);
-        arr[1] = (byte) (val >>> 48);
-        arr[2] = (byte) (val >>> 40);
-        arr[3] = (byte) (val >>> 32);
-        arr[4] = (byte) (val >>> 24);
-        arr[5] = (byte) (val >>> 16);
-        arr[6] = (byte) (val >>> 8);
-        arr[7] = (byte) (val);
+        BIG_ENDIAN_LONG.set(arr, 0, val);
         return arr;
     }
 

+ 99 - 0
server/src/test/java/org/elasticsearch/common/NumbersTests.java

@@ -16,6 +16,8 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.hamcrest.Matchers.is;
+
 public class NumbersTests extends ESTestCase {
 
     @Timeout(millis = 10000)
@@ -46,6 +48,14 @@ public class NumbersTests extends ESTestCase {
             "Value [-1e99999999] is out of range for a long",
             expectThrows(IllegalArgumentException.class, () -> Numbers.toLong("-1e99999999", false)).getMessage()
         );
+        assertEquals(
+            "Value [12345.6] has a decimal part",
+            expectThrows(IllegalArgumentException.class, () -> Numbers.toLong("12345.6", false)).getMessage()
+        );
+        assertEquals(
+            "For input string: \"t12345\"",
+            expectThrows(IllegalArgumentException.class, () -> Numbers.toLong("t12345", false)).getMessage()
+        );
     }
 
     public void testToLongExact() {
@@ -141,4 +151,93 @@ public class NumbersTests extends ESTestCase {
         e = expectThrows(IllegalArgumentException.class, () -> Numbers.toByteExact(new AtomicInteger(3))); // not supported
         assertEquals("Cannot check whether [3] of class [java.util.concurrent.atomic.AtomicInteger] is actually a long", e.getMessage());
     }
+
+    public void testLongToBytes() {
+        assertThat(Numbers.longToBytes(123456L), is(new byte[] { 0, 0, 0, 0, 0, 1, -30, 64 }));
+        assertThat(Numbers.longToBytes(-123456L), is(new byte[] { -1, -1, -1, -1, -1, -2, 29, -64 }));
+        assertThat(Numbers.longToBytes(0L), is(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }));
+        assertThat(Numbers.longToBytes(Long.MAX_VALUE + 1), is(new byte[] { -128, 0, 0, 0, 0, 0, 0, 0 }));
+        assertThat(Numbers.longToBytes(Long.MAX_VALUE + 127), is(new byte[] { -128, 0, 0, 0, 0, 0, 0, 126 }));
+        assertThat(Numbers.longToBytes(Long.MIN_VALUE - 1), is(new byte[] { 127, -1, -1, -1, -1, -1, -1, -1 }));
+        assertThat(Numbers.longToBytes(Long.MIN_VALUE - 127), is(new byte[] { 127, -1, -1, -1, -1, -1, -1, -127 }));
+    }
+
+    public void testBytesToLong() {
+        assertThat(Numbers.bytesToLong(new byte[] { 0, 0, 0, 0, 0, 1, -30, 64 }, 0), is(123456L));
+        assertThat(Numbers.bytesToLong(new byte[] { -1, -1, -1, -1, -1, -2, 29, -64 }, 0), is(-123456L));
+        assertThat(Numbers.bytesToLong(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0), is(0L));
+        assertThat(Numbers.bytesToLong(new byte[] { -128, 0, 0, 0, 0, 0, 0, 0 }, 0), is(Long.MIN_VALUE));
+        assertThat(Numbers.bytesToLong(new byte[] { -128, 0, 0, 0, 0, 0, 0, 126 }, 0), is(Long.MIN_VALUE + 127 - 1));
+        assertThat(Numbers.bytesToLong(new byte[] { 127, -1, -1, -1, -1, -1, -1, -1 }, 0), is(Long.MAX_VALUE));
+        assertThat(Numbers.bytesToLong(new byte[] { 127, -1, -1, -1, -1, -1, -1, -127, 0 }, 0), is(Long.MAX_VALUE - 127 + 1));
+
+        assertThat(Numbers.bytesToLong(new byte[] { 100, 0, 0, 0, 0, 0, 1, -30, 64 }, 1), is(123456L));
+        assertThat(Numbers.bytesToLong(new byte[] { -100, -1, -1, -1, -1, -1, -2, 29, -64 }, 1), is(-123456L));
+    }
+
+    public void testIntToBytes() {
+        assertThat(Numbers.intToBytes(123456), is(new byte[] { 0, 1, -30, 64 }));
+        assertThat(Numbers.intToBytes(-123456), is(new byte[] { -1, -2, 29, -64 }));
+        assertThat(Numbers.intToBytes(0), is(new byte[] { 0, 0, 0, 0 }));
+        assertThat(Numbers.intToBytes(Integer.MAX_VALUE + 1), is(new byte[] { -128, 0, 0, 0 }));
+        assertThat(Numbers.intToBytes(Integer.MAX_VALUE + 127), is(new byte[] { -128, 0, 0, 126 }));
+        assertThat(Numbers.intToBytes(Integer.MIN_VALUE - 1), is(new byte[] { 127, -1, -1, -1 }));
+        assertThat(Numbers.intToBytes(Integer.MIN_VALUE - 127), is(new byte[] { 127, -1, -1, -127 }));
+    }
+
+    public void testBytesToInt() {
+        assertThat(Numbers.bytesToInt(new byte[] { 0, 1, -30, 64 }, 0), is(123456));
+        assertThat(Numbers.bytesToInt(new byte[] { -1, -2, 29, -64 }, 0), is(-123456));
+        assertThat(Numbers.bytesToInt(new byte[] { 0, 0, 0, 0 }, 0), is(0));
+        assertThat(Numbers.bytesToInt(new byte[] { -128, 0, 0, 0 }, 0), is(Integer.MIN_VALUE));
+        assertThat(Numbers.bytesToInt(new byte[] { -128, 0, 0, 126 }, 0), is(Integer.MIN_VALUE + 127 - 1));
+        assertThat(Numbers.bytesToInt(new byte[] { 127, -1, -1, -1 }, 0), is(Integer.MAX_VALUE));
+        assertThat(Numbers.bytesToInt(new byte[] { 127, -1, -1, -127, 0 }, 0), is(Integer.MAX_VALUE - 127 + 1));
+
+        assertThat(Numbers.bytesToInt(new byte[] { 100, 0, 1, -30, 64 }, 1), is(123456));
+        assertThat(Numbers.bytesToInt(new byte[] { -100, -1, -2, 29, -64 }, 1), is(-123456));
+    }
+
+    public void testShortToBytes() {
+        assertThat(Numbers.shortToBytes(1234), is(new byte[] { 4, -46 }));
+        assertThat(Numbers.shortToBytes(-1234), is(new byte[] { -5, 46 }));
+        assertThat(Numbers.shortToBytes(0), is(new byte[] { 0, 0 }));
+        assertThat(Numbers.shortToBytes(Short.MAX_VALUE + 1), is(new byte[] { -128, 0 }));
+        assertThat(Numbers.shortToBytes(Short.MAX_VALUE + 127), is(new byte[] { -128, 126 }));
+        assertThat(Numbers.shortToBytes(Short.MIN_VALUE - 1), is(new byte[] { 127, -1 }));
+        assertThat(Numbers.shortToBytes(Short.MIN_VALUE - 127), is(new byte[] { 127, -127 }));
+    }
+
+    public void testBytesToShort() {
+        assertThat(Numbers.bytesToShort(new byte[] { 4, -46 }, 0), is((short) 1234));
+        assertThat(Numbers.bytesToShort(new byte[] { -5, 46 }, 0), is((short) -1234));
+        assertThat(Numbers.bytesToShort(new byte[] { 0, 0 }, 0), is((short) 0));
+        assertThat(Numbers.bytesToShort(new byte[] { -128, 0 }, 0), is(Short.MIN_VALUE));
+        assertThat(Numbers.bytesToShort(new byte[] { -128, 126 }, 0), is((short) (Short.MIN_VALUE + 127 - 1)));
+        assertThat(Numbers.bytesToShort(new byte[] { 127, -1 }, 0), is(Short.MAX_VALUE));
+        assertThat(Numbers.bytesToShort(new byte[] { 127, -127, 0 }, 0), is((short) (Short.MAX_VALUE - 127 + 1)));
+
+        assertThat(Numbers.bytesToShort(new byte[] { 100, 0, 1, 4, -46 }, 3), is((short) 1234));
+        assertThat(Numbers.bytesToShort(new byte[] { -100, -1, -2, -5, 46 }, 3), is((short) -1234));
+    }
+
+    public void testDoubleToBytes() {
+        assertThat(Numbers.doubleToBytes(-1234.0d), is(new byte[] { -64, -109, 72, 0, 0, 0, 0, 0 }));
+        assertThat(Numbers.doubleToBytes(1234.0d), is(new byte[] { 64, -109, 72, 0, 0, 0, 0, 0 }));
+        assertThat(Numbers.doubleToBytes(.0d), is(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }));
+    }
+
+    public void testIsPositiveNumeric() {
+        assertTrue(Numbers.isPositiveNumeric(""));
+        assertTrue(Numbers.isPositiveNumeric("0"));
+        assertTrue(Numbers.isPositiveNumeric("1"));
+        assertFalse(Numbers.isPositiveNumeric("1.0"));
+        assertFalse(Numbers.isPositiveNumeric("-1"));
+        assertFalse(Numbers.isPositiveNumeric("test"));
+        assertTrue(Numbers.isPositiveNumeric("9223372036854775807000000"));
+        assertEquals(
+            "Cannot invoke \"String.length()\" because \"string\" is null",
+            expectThrows(NullPointerException.class, () -> Numbers.isPositiveNumeric(null)).getMessage()
+        );
+    }
 }