我有一个基于web的Java应用程序,用于生成会话信息的随机UUID。我们的一位测试人员声称根据他自己的分析,生成UUID需要高达350毫秒的时间,但我还没有能够复制他的结果。他指出这篇文章http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html来支持他的结果。我想看看其他人是否在Java 6或Java 7应用程序中使用Java内置的UUID生成功能时遇到了类似的限制。
我有一个基于web的Java应用程序,用于生成会话信息的随机UUID。我们的一位测试人员声称根据他自己的分析,生成UUID需要高达350毫秒的时间,但我还没有能够复制他的结果。他指出这篇文章http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html来支持他的结果。我想看看其他人是否在Java 6或Java 7应用程序中使用Java内置的UUID生成功能时遇到了类似的限制。
请记住,这个测试是 不现实的,超出了我能想象到的任何最坏情况。我的目标是让那些没有事实支持批评UUID使用的人安静下来。
场景:
java.util.UUID.randomUUID()
的百万次紧密循环
在一个线程中运行一个循环,因此没有对同步方法/类的争用。
// 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 ) + " )" );
而定义每个线程的类...
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微秒。
运行时间为14、20、20、23和24微秒每个UUID(不按顺序)。因此,在极端争用情况下,只差了约10倍,20微秒在我所知的任何实际应用中都是可以接受的。
nanos / 1000
,这是微秒,而不是毫秒。请纠正我如果我错了。 - Groostav我测试过了
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毫秒
random.nextLong()
策略就足够了。 - Groostavjava.util.Random
来提高性能,而在我看来这是一个可怕的想法,除非你完全理解后果,这就是我说当他们可以被轻易地暴力破解时我所阐述的。 - Mikkel LøkkeUUID的随机形式通常使用“加密强度”随机数源。
如果不这样做,那么所谓的随机UUID就会变得可预测,并且给定UUID被重新发行的概率可能会增加到令人担忧的水平。正如另一个答案建议的那样,您可以为UUID构造函数提供快速(但弱)的伪随机数生成器。但这是一个坏主意。
典型的加密强度随机数生成器使用外部应用程序的熵源。它可能是硬件随机数生成器,但更常见的是在正常运行中由操作系统收集的“随机性”。问题是熵源有速率限制。如果你在一段时间内超过了那个速率,你就会耗尽资源。接下来会发生什么取决于系统,但在某些系统上,读取熵的系统调用将停顿……直到更多的熵可用。
我认为这就是客户端系统上正在发生的事情。(在虚拟机上很常见…)
Linux系统的一个hacky解决方案是安装rngd守护进程并配置它使用良好的伪随机数生成器“填充”熵池。一个安全专家会指出:
我不确定这个hack在实践中有多安全。
以下是关于慢随机数生成的另一个问题的问答:
线程数量对UUID生成的性能有很大影响。看一下SecureRandom#nextBytes(byte[]
方法的实现,它为UUID.randomUUID()
生成随机数:
synchronized public void nextBytes(byte[] bytes) {
secureRandomSpi.engineNextBytes(bytes);
}
nextBytes
方法是synchronized
的,当被不同线程访问时会导致显著的性能损失。
考虑使用版本1的UUID类型如何?
版本1 基于MAC地址和当前时间("时空")。比版本4更不容易发生冲突。
版本4 完全基于使用加密强随机数生成器生成的随机数字。
Oracle JVM没有提供版本1生成器,显然是出于安全和隐私方面的考虑。JVM不提供访问主机机器的MAC地址。
至少有一个第三方库可用,它提供了版本1 UUID以及其他版本:JUG - Java UUID Generator。他们说Java 6中引入的功能使他们可以访问MAC地址。
阅读2010年文章中使用Java UUID生成器版本3进行性能讨论的测试结果,更多关于Java UUID生成器(JUG)的内容,有关性能的说明。Tatu Saloranta在他的MacBook上测试了各种类型的UUID。
结论: MAC+时间版本比随机版本快20倍。
基于时间的变体(以太网地址加时间戳)要快得多 - 几乎比默认的基于随机的变体快20倍 - 每秒生成约500万个UUID。
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毫秒。
UUID.randomUUID();
这一行代码吗?因为它的结果没有被使用。 - Dariusz Mydlarz我做了和其他人一样的测试,我的结果是每个UUID生成大约需要300纳秒。测试结果基于一台i7四核WIN7 64位电脑,并且我尝试使用了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