【问题标题】:Why is the initial call to UUID.randomUUID() slow?为什么对 UUID.randomUUID() 的初始调用很慢?
【发布时间】:2017-06-07 10:54:57
【问题描述】:

给定以下代码 sn-p,它生成 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
  • 由于网络接口扫描等原因,在 Windows 上 SecureRandom 的初始化可能非常慢。有关详细信息,请参阅 this question

标签: java performance uuid


【解决方案1】:

这是一次完成的 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

确实,单发时间远低于平均水平。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多