UUID creation performance
Brett Okken
brett.okken.os at gmail.com
Sun Mar 5 23:49:23 UTC 2023
The new ByteArray class works great for the nameUUIDFromBytes method,
which must be in big endian.
For randomUUID, byte order does not matter, so using native would be
fastest, but there does not appear to be a utility class for that.
Is there a preference of just having a native order VarHandle to use
in UUID vs. having a utility method which chooses which utility class
to call based on the native order vs. some other option?
Thanks,
Brett
On Wed, Mar 1, 2023 at 9:08 AM Roger Riggs <roger.riggs at oracle.com> wrote:
>
> 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());
> > }
> > }
>
More information about the core-libs-dev
mailing list