为什么调用UUID.randomUUID()的初始时间很慢?

5

以下是生成 UUID.randomUUID() 的代码片段,我得到的性能结果(以毫秒为单位)如下:

public static void main(String[] args) {
    long tmp = System.currentTimeMillis();
    UUID.randomUUID();
    tmp = printDiff(tmp);
    UUID.randomUUID();
    tmp = printDiff(tmp);
    UUID.randomUUID();
    tmp = printDiff(tmp);
    UUID.randomUUID();
    tmp = printDiff(tmp);
}

private static long printDiff(final long previousTimestamp) {
    long tmp = System.currentTimeMillis();
    System.out.printf("%s%n", tmp - previousTimestamp);
    return tmp;
}

结果:

971
6
0
0

JDK: 1.8 操作系统: Windows 7

为什么只有初始调用需要这么长时间?(接近1秒!)


@4castle 不是这样的,这里只有4个调用,没有JIT。 - Eugene
1
在Windows上,由于网络接口扫描等原因,SecureRandom的初始化可能非常缓慢。有关详细信息,请参见此问题。 - apangin
3个回答

7

这是只执行一次的SecureRandom初始化:

//from the source code of randomUUID
private static class Holder {
    static final SecureRandom numberGenerator = new SecureRandom();
}

但这还不是全部。这些零应该真正地跳到你的脸上。所以操作只花费了0毫秒,这意味着它们花费了更少的时间吗?比如几个纳秒或者你做错了什么吗?

有一个适当的工具来测量这些事情,叫做jmh。

@BenchmarkMode({ Mode.AverageTime, Mode.SingleShotTime })
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class UUIDRandom {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder().include(UUIDRandom.class.getSimpleName()).build();
        new Runner(opt).run();
    }

    @Benchmark
    @Fork(1)
    public UUID random() {
        return UUID.randomUUID();
    }
}

输出结果如下:

Benchmark          Mode  Cnt  Score   Error  Units
UUIDRandom.random  avgt    2  0.002          ms/op
UUIDRandom.random    ss    2  0.094          ms/op

事实上,单次拍摄时间远比平均时间更糟糕。


0

UUID.randomUUID()第一次被调用时,它必须初始化一些内部对象,这些对象在所有后续调用中都会使用。

UUID.randomUUID的源代码如下:

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

    byte[] randomBytes = new byte[16];
    ng.nextBytes(randomBytes);
    randomBytes[6]  &= 0x0f;  /* clear version        */
    randomBytes[6]  |= 0x40;  /* set to version 4     */
    randomBytes[8]  &= 0x3f;  /* clear variant        */
    randomBytes[8]  |= 0x80;  /* set to IETF variant  */
    return new UUID(randomBytes);
}

在这里,Holder.numberGenerator 是一个全局变量,第一次使用时必须进行初始化:
private static class Holder {
    static final SecureRandom numberGenerator = new SecureRandom();
}

0

根据Java 8代码,创建一个SecureRandom对象似乎是很昂贵的。这就是为什么它们推迟初始化直到需要时(也称为延迟初始化),并在后续调用中重复使用它。

/*
 * The random number generator used by this class to create random
 * based UUIDs. In a holder class to defer initialization until needed.
 */
private static class Holder {
    static final SecureRandom numberGenerator = new SecureRandom();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接