18

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

4

7 回答 7

24

这是 beta 127 中的测试运行。

请记住,这个测试是不切实际的,超出了我能想象的任何最坏的情况。我的目标是平息那些在没有事实支持他们批评的情况下使用UUID的人。

设想:

  • 一百万次调用的紧密循环java.util.UUID.randomUUID()
    • 仅此一项测试。(没有争议)
    • 一项有争用的测试,其中 2 个其他线程处于紧密循环中,进行1000万次调用。
  • Java 8 测试版 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 年末)

无争

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

// 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 ) + " )" );

结果

每个 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 ) + " )" );

以及定义每个线程的类……</p>

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

    }
}

结果

每个 UUID大约20 微秒。

每个 UUID 的运行时间分别为 14、20、20、23 和 24 微秒(不按此顺序)。因此,在极端竞争下仅差 10 倍左右,在我所知道的任何实际使用中,20 微秒都是可以接受的。

于 2014-02-04T00:50:40.877 回答
14

UUID 的随机形式通常使用“密码强度”随机数的来源。

(如果没有,那么所谓的随机 UUID 将是可预测的,并且重新发布给定 UUID 的概率可能会增加到令人担忧的水平。正如另一个答案所暗示的那样,您可以为UUID构造函数提供一个快速(但较弱)的 PRNG。但那将是一个坏主意。)

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

我希望这就是您客户系统上正在发生的事情。(这在虚拟机上并不少见......)

一个 hacky 解决方法(对于 Linux 系统)是安装rngd守护程序并将其配置为使用良好的伪随机数生成器“充值”熵池。安全专家会指出:

  • 这将影响您的 UUID 生成器的随机性,并且
  • 熵池用于其他与安全相关的事物,因此从可疑来源对其进行充值会削弱系统上许多事物的安全性。

我不确定这种黑客在实践中的安全性。

这是关于慢速随机数生成主题的另一个问答:

于 2013-01-26T02:54:23.840 回答
14

我测试了它

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

在我的电脑上大约是 1100 毫秒,这很慢。UUID.randomUUID() 在内部使用 SecureRandom,为了使其更快,我们可以使用常规 java.util.Random

    Random r = new Random();
    for (;;) {
            ..
            new UUID(r.nextLong(), r.nextLong());

大约 80 毫秒

于 2013-01-26T05:19:20.323 回答
9

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

synchronized public void nextBytes(byte[] bytes) {
    secureRandomSpi.engineNextBytes(bytes);
}

nextBytes当被synchronized不同的线程访问时,这会导致显着的性能损失。

于 2014-03-07T12:43:02.313 回答
5

使用版本 1 而不是 4

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

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

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

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

壶库

至少有一个第三方库可以提供版本 1 UUID 以及其他版本:JUG – Java UUID Generator。他们说 Java 6 中引入的特性让他们可以访问 MAC 地址。

测试结果:20 倍

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

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

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

于 2014-01-07T18:38:23.697 回答
1

在 jdk 1.7.0_40 下运行的 junit 测试:

package org.corba.util;

import org.junit.Test;
import org.springframework.util.StopWatch;

import java.util.UUID;

/**
 * Test of performance of Java's UUID generation
 * @author Corba Da Geek
 * Date: 1/6/14
 * Time: 3:48 PM
 */
public class TestRandomUUID {
    private static final int ITERATIONS = 1000000;

    @Test
    public void testRandomUUID() throws Exception {
        // Set up data
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // Run test
        for (int i = 0; i < ITERATIONS; i++)
            UUID.randomUUID();

        // Check results
        stopWatch.stop();
        final long totalTimeMillis = stopWatch.getTotalTimeMillis();
        System.out.println("Number of milliseconds: " + totalTimeMillis + " for " + ITERATIONS + " iterations.");
        System.out.println(String.format("Average time per iteration: %.7f ms", (float)totalTimeMillis/ITERATIONS));
    }
}

我的 i5 笔记本电脑上的结果是:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.corba.util.TestRandomUUID
Number of milliseconds: 677 for 1000000 iterations.
Average time per iteration: 0.0006770 ms
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.746 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

每次调用 0.0006770 毫秒

于 2014-01-07T16:35:40.270 回答
0

我和其他人做了同样的测试,我的结果更像是 300 NANOseconds per UUID generation。结果在 i7 四核 WIN7 64 PC 上。我尝试使用 jdk1.7.0_67 和 jdk1.8.0_40 64 位 JVM。

我有点困惑,我的结果与其他所有结果都如此不同......但是生成随机数的 1 毫秒似乎很多!

public static void main(String[] args) throws Exception {
    long start = System.nanoTime();
    int loops = 1000000; // One million.

    long foo = 0;

    for (int i = 0; i < loops; i++) {
        UUID uuid = java.util.UUID.randomUUID();

        //this is just to make sure there isn't some kind of optimization
        //that would prevent the actual generation
        foo += (uuid.getLeastSignificantBits()
                + uuid.getMostSignificantBits());
    }

    long stop = System.nanoTime();

    long elapsed = (stop - start);

    System.out.println(String.format("UUIDs              : %,d", loops));
    System.out.println(String.format("Total time (ns)    : %,d", elapsed));
    System.out.println(String.format("Time per UUID (ns) : %,d", (elapsed / loops)));

    System.out.println();
    System.out.println(foo);
}

输出 :

UUIDs              : 1 000 000
Total time (ns)    : 320 715 288
Time per UUID (ns) : 320

5372630452959404665
于 2015-04-13T14:32:05.350 回答