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());
}
}