我有一个基于 Web 的 Java 应用程序,它为会话信息生成随机 UUID。我们的一位测试人员声称根据他自己的分析生成 UUID 最多需要 350 毫秒,但我还没有能够复制他的结果。他指向这篇文章http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html来帮助支持他的结果。我想看看是否有其他人在 Java 6 或 Java 7 应用程序中使用 Java 内置的 UUID 生成功能遇到了这个限制。
7 回答
请记住,这个测试是不切实际的,超出了我能想象的任何最坏的情况。我的目标是平息那些在没有事实支持他们批评的情况下使用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 运行
- 在虚拟机中执行
- Parallels 9虚拟机
- 山狮
- 3个虚拟核心
- 4 场演出记忆
- 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 微秒都是可以接受的。
UUID 的随机形式通常使用“密码强度”随机数的来源。
(如果没有,那么所谓的随机 UUID 将是可预测的,并且重新发布给定 UUID 的概率可能会增加到令人担忧的水平。正如另一个答案所暗示的那样,您可以为UUID
构造函数提供一个快速(但较弱)的 PRNG。但那将是一个坏主意。)
典型的加密强度随机数生成器使用应用程序外部的熵源。它可能是一个硬件随机数生成器,但更常见的是,它是操作系统在正常操作中收集的累积“随机性”。问题是熵的来源有一个速率限制。如果您在一段时间内超过该速率,则可以耗尽源。接下来发生的事情取决于系统,但在某些系统上,读取熵的系统调用将停止......直到有更多可用。
我希望这就是您客户系统上正在发生的事情。(这在虚拟机上并不少见......)
一个 hacky 解决方法(对于 Linux 系统)是安装rngd
守护程序并将其配置为使用良好的伪随机数生成器“充值”熵池。安全专家会指出:
- 这将影响您的 UUID 生成器的随机性,并且
- 熵池用于其他与安全相关的事物,因此从可疑来源对其进行充值会削弱系统上许多事物的安全性。
我不确定这种黑客在实践中的安全性。
这是关于慢速随机数生成主题的另一个问答:
我测试了它
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 毫秒
线程的数量对 UUID 的生成性能有巨大的影响。这可以通过查看SecureRandom#nextBytes(byte[]
生成随机数的实现来解释UUID.randomUUID()
:
synchronized public void nextBytes(byte[] bytes) {
secureRandomSpi.engineNextBytes(bytes);
}
nextBytes
当被synchronized
不同的线程访问时,这会导致显着的性能损失。
使用版本 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。
在 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 毫秒。
我和其他人做了同样的测试,我的结果更像是 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