使用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); }
nextBytes
是synchronized
,当由不同的线程访问时会导致显着的性能损失。
使用版本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