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

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

我测试了它

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毫秒

这是一个测试运行 测试版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运行
  • 在虚拟机内执行
    • Parallels 9虚拟机
    • 山狮
    • 3个虚拟核心
    • 4演出记忆
  • 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 ) + " )" ); 

结果

每个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微秒

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

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

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

我希望客户系统上发生的事情就是这样。

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

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

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

nextBytessynchronized ,当由不同的线程访问时会导致显着的性能损失。

使用版本1而不是4

如何使用版本1类型的UUID?

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

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

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

JUG图书馆

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

测试结果:20x

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

Upshot: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笔记本电脑的结果是:

 ------------------------------------------------------- TESTS ------------------------------------------------------- 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 ms

我做了与其他测试相同的测试, 我的结果更像每个UUID生成300 NANOseconds 。 结果在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