使用Java 7或Java 6生成随机UUID的性能

Sha*_*n H 17 java uuid

我有一个基于Web的Java应用程序,它为会话信息生成随机UUID.我们的一位测试人员根据他自己的分析声称最多可以生成350毫秒来生成UUID,但我还没有能够复制他的结果.他指出这篇文章http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html来帮助支持他的结果.我想看看是否有其他人在Java 6或Java 7应用程序中使用Java的内置UUID生成功能遇到了这个限制.

Bas*_*que 19

这是测试 版的测试版127.

请记住,这个测试是不现实的,超出了我能想象的最坏情况.我的目标是安静那些在没有事实的情况下对UUID进行恶意使用的人来支持他们的批评.

场景:

  • 一百万次呼叫的紧密循环 java.util.UUID.randomUUID()
    • 只有这一点的一个测试.(没有争论)
    • 一个有争用的测试,其中2个其他线程处于紧密循环中,进行了千万次调用.
  • Java 8 beta 127
    • java版"1.8.0"
    • Java(TM)SE运行时环境(版本1.8.0-b127)
    • Java HotSpot(TM)64位服务器VM(内置25.0-b69,混合模式)
  • 从Netbeans 7.4 IDE运行
  • 在虚拟机内执行
  • Mac mini(2012年末)
    • 小牛
    • 具有超线程的Intel i7四核(8个视在内核)
    • 16演出记忆

没有争用

在一个线程中运行一个循环,因此不会对同步的方法/类进行争用.

// Warm the random generator.
java.util.UUID uuid;
uuid = java.util.UUID.randomUUID();

long stop = 0;
long start = System.nanoTime();

int loops = 1000000;  // One million.
for ( int i = 0; i < loops; i++ ) {
    uuid = java.util.UUID.randomUUID();
}

stop = System.nanoTime();

long elapsed = ( stop - start );

System.out.println( "UUIDs: " + loops );
System.out.println( "Nanos: " + elapsed );
System.out.println( "Nanos per uuid: " + ( elapsed / loops ) + " ( micros per: " + ( elapsed / loops / 1000 ) + " )" );
Run Code Online (Sandbox Code Playgroud)

结果

每个UUID 约2微秒.

有争用

与上面类似,但在进行一百万次调用的循环时,我们还有两个其他线程在运行,每个线程都有千万次调用.

// Warm the random generator.
java.util.UUID uuid;
uuid = java.util.UUID.randomUUID();

int pass = 10_000_000 ;  // Ten million.
MyThread t1 = new MyThread( pass );
MyThread t2 = new MyThread( pass );


t1.start();
t2.start();
t3.start();

long stop = 0;
long start = System.nanoTime();

int loops = 1_000_000 ;  // One million.
for ( int i = 0; i < loops; i++ ) {
    uuid = java.util.UUID.randomUUID();
}

stop = System.nanoTime();

long elapsed = ( stop - start );

System.out.println( "UUIDs: " + loops );
System.out.println( "Nanos: " + elapsed );
System.out.println( "Nanos per uuid: " + ( elapsed / loops ) + " ( micros per: " + ( elapsed / loops / 1000 ) + " )" );
Run Code Online (Sandbox Code Playgroud)

并且定义每个线程的类......

class MyThread extends Thread {

    private int loops;

    public MyThread( int loops ) {
        this.loops = loops;
    }

    @Override
    public void run() {
        java.util.UUID uuid;
        for ( int i = 0; i < this.loops; i++ ) {
            uuid = java.util.UUID.randomUUID();
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

结果

每个UUID 约20微秒.

每个UUID运行14,20,20,23和24微秒(不按此顺序).所以在极端争用下只差了大约10倍,在我所知道的任何实际使用中都可以接受20微秒.

  • 呃,如果我错了,请纠正我,但你的数学是'nanos/1000`,这是_micro_秒,**不是**_milli_秒. (5认同)
  • 您在任何地方使用变量 uuid 吗?如果您不使用它,您可能不会测试任何内容,因为 JVM 在检测到未使用变量时会优化代码。查看 Scott Oaks 撰写的《Java 性能》。您可能需要声明一个易失性字段,以确保基准测试正在测量任何有用的东西。 (2认同)

Ste*_*n C 12

随机形式的UUID需要"加密强度"随机数源.(如果没有那么可能会重新发出给定UUID的可能性会增加到令人担忧的水平.)

典型的加密随机数生成器使用应用程序外部的熵源.它可能是硬件随机数生成器,但更常见的是它是在正常操作中由操作系统收集的累积"随机性".问题是熵源具有速率限制.如果您在一段时间内超过该速率,则可以耗尽源.接下来发生的是系统相关的,但在某些系统上,读取熵的系统调用将停止......直到有更多可用.

我希望这是您客户的系统上发生的事情.

一种解决方法(对于Linux系统)是安装UUID守护程序并将其配置为使用伪随机数生成器"充值"熵池.缺点是这可能会损害您的UUID生成器的随机性.

  • 我认为我们也遇到了“缺乏熵”的问题。UUID生成很快就开始了,但是几个小时后便可以进行爬网了。在无头虚拟机上运行可能是导致此问题的原因,因为操作系统的熵源较少。有关更多信息,请参见http://stackoverflow.com/questions/137212/how-to-solve-performance-problem-with-java-securerandom。 (2认同)

Evg*_*eev 9

我测试了它

    for (;;) {
        long t0 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            UUID.randomUUID();
        }
        System.out.println(System.currentTimeMillis() - t0);
    }
Run Code Online (Sandbox Code Playgroud)

在我的电脑上它是~1100毫秒,这是非常慢的.UUID.randomUUID()在内部使用SecureRandom,使其更快,我们可以使用常规的java.util.Random

    Random r = new Random();
    for (;;) {
            ..
            new UUID(r.nextLong(), r.nextLong());
Run Code Online (Sandbox Code Playgroud)

它是~80毫秒

  • 它使用SecureRandom是有原因的,你不应该"使用常规的java.util.Random",而不要指望UUID冲突. (13认同)
  • 这些数字没有通过气味测试.1110ms生成100万随机UUID声音的方式很快.但1110ms生成1 UUID听起来太慢了. (4认同)
  • @MikkelLøkke我不确定您的推理是否正确。我认为最大的问题是可预测性,而不是均匀性。UUID可能用于安全目的-作为不可猜测的密钥-需要高质量的熵。但是在很多情况下,目标是不安全地避免碰撞,我相信`random.nextLong()`策略就足够了。 (2认同)

Pet*_*ler 9

线程数对UUID生成的性能产生巨大影响.这可以通过查看其实现SecureRandom#nextBytes(byte[]生成随机数来解释UUID.randomUUID():

synchronized public void nextBytes(byte[] bytes) {
    secureRandomSpi.engineNextBytes(bytes);
}
Run Code Online (Sandbox Code Playgroud)

nextBytessynchronized时,由不同的线程访问导致显著的性能损失.


Bas*_*que 6

使用版本 1 而不是 4

\n\n

使用版本 1 类型的 UUID 怎么样?

\n\n

版本 1基于MAC 地址和当前时间(“空间和时间”)。与版本 4 相比,发生冲突的可能性要小得多。

\n\n

版本 4完全基于使用加密的强随机生成器从随机数生成。

\n\n

Oracle JVM 不提供版本 1 生成器,显然是出于安全和隐私方面的考虑。JVM 不提供对主机 MAC 地址的访问。

\n\n

JUG图书馆

\n\n

至少有一个第三方库可以提供版本 1 UUID 以及其他版本:JUG \xe2\x80\x93 Java UUID Generator。他们表示,Java 6 中引入的功能使他们能够访问 MAC 地址。

\n\n

测试结果:20x

\n\n

请阅读2010 年文章“更多关于 Java UUID 生成器 (JUG) ,谈性能”中关于使用Java UUID 生成器版本 3 的性能和测试结果的讨论。Tatu Saloranta 在他的 MacBook 上测试了各种 UUID。

\n\n

结果:MAC+Time 版本比随机版本快 20 倍。

\n\n
\n

基于时间的变体(以太网地址加时间戳)要快得多,几乎是基于随机的默认变体的 20 倍,每秒生成约 500 万个 UUID。

\n
\n