Hi,

That's an interesting idea.  Recently VarHandle access methods were created by JDK-8300236 [1] [2] in the jdk.internal.util package. See the ByteArray and ByteArrayLittleEndian classes.

See how that would affect performance and leverage existing VarHandles.

Thanks, Roger

[1] https://bugs.openjdk.org/browse/JDK-8300236
[2] https://github.com/openjdk/jdk/pull/12076

On 3/1/23 7:50 AM, Brett Okken wrote:
Is there any interest in updating the static UUID.randomUUID() and
UUID.nameUUIDFromBytes(byte[]) factory methods to use either a
ByteBuffer or byteArrayViewVarHandle to convert the byte[] to 2 long
values then do the bit twiddling?
These methods are really dominated by time to create/populate the
byte[], but this does reduce the time to create the 2 long values by
at least half.
It would also allow the removal of the private UUID(byte[] data).

     public static UUID randomUUID() {
         SecureRandom ng = Holder.numberGenerator;

         byte[] randomBytes = new byte[16];
         ng.nextBytes(randomBytes);
         final ByteBuffer bb = ByteBuffer.wrap(randomBytes);
         bb.order(ByteOrder.nativeOrder());

         long msb = bb.getLong();
         long lsb = bb.getLong();

         msb &= 0xFFFFFFFFFFFF0FFFL;  /* clear version        */
         msb |= 0x4000L;              /* set to version 4     */

         lsb &= 0x3FFFFFFFFFFFFFFFL;  /* clear variant        */
         lsb |= 0x8000000000000000L;  /* set to IETF variant  */

         return new UUID(msb, lsb);
     }

     public static UUID nameUUIDFromBytes(byte[] name) {
         MessageDigest md;
         try {
             md = MessageDigest.getInstance("MD5");
         } catch (NoSuchAlgorithmException nsae) {
             throw new InternalError("MD5 not supported", nsae);
         }
         byte[] md5Bytes = md.digest(name);

         // default byte order is BIG_ENDIAN
         final ByteBuffer bb = ByteBuffer.wrap(md5Bytes);

         long msb = bb.getLong();
         long lsb = bb.getLong();

         msb &= 0xFFFFFFFFFFFF0FFFL;  /* clear version        */
         msb |= 0x3000L;              /* set to version 3     */

         lsb &= 0x3FFFFFFFFFFFFFFFL;  /* clear variant        */
         lsb |= 0x8000000000000000L;  /* set to IETF variant  */

         return new UUID(msb, lsb);
     }

Benchmark                    Mode  Cnt   Score   Error  Units
UUIDBenchmark.jdk_name       avgt    3  11.885 ± 4.025  ns/op
UUIDBenchmark.jdk_random     avgt    3  11.656 ± 0.987  ns/op
UUIDBenchmark.longs          avgt    3   7.618 ± 1.047  ns/op
UUIDBenchmark.longs_bb       avgt    3   7.755 ± 1.643  ns/op
UUIDBenchmark.longs_name     avgt    3   8.467 ± 1.784  ns/op
UUIDBenchmark.longs_name_bb  avgt    3   8.455 ± 1.662  ns/op
UUIDBenchmark.randomBytes    avgt    3   6.132 ± 0.447  ns/op


@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 2, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class UUIDBenchmark {

     private static final VarHandle LONGS_ACCESS =
MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());

     private static final VarHandle BE_LONGS_ACCESS =
MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.BIG_ENDIAN);

     @Benchmark
     public byte[] randomBytes() {
         final byte[] bytes = new byte[16];
         randomBytes(bytes);
         return bytes;
     }

     @Benchmark
     public void jdk_random(Blackhole bh) {
         final byte[] data = new byte[16];
         randomBytes(data);
         data[6]  &= 0x0f;  /* clear version        */
         data[6]  |= 0x40;  /* set to version 4     */
         data[8]  &= 0x3f;  /* clear variant        */
         data[8]  |= 0x80;  /* set to IETF variant  */
         long msb = 0;
         long lsb = 0;
         assert data.length == 16 : "data must be 16 bytes in length";
         for (int i=0; i<8; i++)
             msb = (msb << 8) | (data[i] & 0xff);
         for (int i=8; i<16; i++)
             lsb = (lsb << 8) | (data[i] & 0xff);
         bh.consume(msb);
         bh.consume(lsb);
     }

     @Benchmark
     public void jdk_name(Blackhole bh)
     {
         final byte[] md5Bytes = new byte[16];
         randomBytes(md5Bytes);
         md5Bytes[6]  &= 0x0f;  /* clear version        */
         md5Bytes[6]  |= 0x30;  /* set to version 3     */
         md5Bytes[8]  &= 0x3f;  /* clear variant        */
         md5Bytes[8]  |= 0x80;  /* set to IETF variant  */
         long msb = 0;
         long lsb = 0;
         assert md5Bytes.length == 16 : "data must be 16 bytes in length";
         for (int i=0; i<8; i++)
             msb = (msb << 8) | (md5Bytes[i] & 0xff);
         for (int i=8; i<16; i++)
             lsb = (lsb << 8) | (md5Bytes[i] & 0xff);
         bh.consume(msb);
         bh.consume(lsb);
     }

     @Benchmark
     public void longs(Blackhole bh) {
         final byte[] data = new byte[16];
         randomBytes(data);

         long msb = (long) LONGS_ACCESS.get(data, 0);
         long lsb = (long) LONGS_ACCESS.get(data, 8);

         msb &= 0xFFFFFFFFFFFF0FFFL;
         msb |= 0x4000L;

         lsb &= 0x3FFFFFFFFFFFFFFFL;
         lsb |= 0x8000000000000000L;

         bh.consume(msb);
         bh.consume(lsb);
     }

     @Benchmark
     public void longs_name(Blackhole bh) {
         final byte[] data = new byte[16];
         randomBytes(data);

         long msb = (long) BE_LONGS_ACCESS.get(data, 0);
         long lsb = (long) BE_LONGS_ACCESS.get(data, 8);

         msb &= 0xFFFFFFFFFFFF0FFFL;
         msb |= 0x3000L;

         lsb &= 0x3FFFFFFFFFFFFFFFL;
         lsb |= 0x8000000000000000L;

         bh.consume(msb);
         bh.consume(lsb);
     }

     @Benchmark
     public void longs_bb(Blackhole bh) {
         final byte[] data = new byte[16];
         randomBytes(data);

         final ByteBuffer bb = ByteBuffer.wrap(data);
         bb.order(ByteOrder.nativeOrder());

         long msb = bb.getLong();
         long lsb = bb.getLong();

         msb &= 0xFFFFFFFFFFFF0FFFL;
         msb |= 0x4000L;

         lsb &= 0x3FFFFFFFFFFFFFFFL;
         lsb |= 0x8000000000000000L;

         bh.consume(msb);
         bh.consume(lsb);
     }

     @Benchmark
     public void longs_name_bb(Blackhole bh) {
         final byte[] data = new byte[16];
         randomBytes(data);

         final ByteBuffer bb = ByteBuffer.wrap(data);
//        bb.order(ByteOrder.BIG_ENDIAN);

         long msb = bb.getLong();
         long lsb = bb.getLong();

         msb &= 0xFFFFFFFFFFFF0FFFL;
         msb |= 0x3000L;

         lsb &= 0x3FFFFFFFFFFFFFFFL;
         lsb |= 0x8000000000000000L;

         bh.consume(msb);
         bh.consume(lsb);
     }

     static void randomBytes(byte[] bytes) {
         ThreadLocalRandom tlr = ThreadLocalRandom.current();
         LONGS_ACCESS.set(bytes, 0, tlr.nextLong());
         LONGS_ACCESS.set(bytes, 8, tlr.nextLong());
     }
}

Reply via email to