【问题标题】:Performance of Random UUID generation with Java 7 or Java 6使用 Java 7 或 Java 6 生成随机 UUID 的性能
【发布时间】:2013-01-10 01:49:45
【问题描述】:

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

【问题讨论】:

  • 350 毫秒生成 一个 UUID?他如何用您链接的文章来支持这一点?
  • 这是他正在使用的分析工具。我还没有能够在我的末端复制。这就是为什么我想检查是否有其他人遇到过类似的问题。这可能是分析工具的问题,也可能是 Java 与运行时环境的奇怪组合。我也觉得很奇怪。
  • 再一次:他是否声称生成 一个 UUID 需要 350 毫秒(因为您将 UUID 写成复数形式,但没有实际指定多少)?他如何用您链接到的文章来支持这一点?那篇文章中没有任何内容表明 UUID 生成器如此缓慢。另一个问题:测试者在哪个操作系统上运行它的测试? Linux 上的 Java 使用 /dev/urandom 生成器,如果该系统上没有太多活动(例如用户输入或网络流量),它可能会相当慢。
  • 声称生成单个 UUID 需要 350 毫秒。我们的默认系统是运行 Mountain Lion 的 Macbook Pro。生产系统是 CentOS 的最新版本。
  • 350 毫秒?我在 C 中创建了one,在较新的 macbook pro 上每秒生成 400,000 个 UUID。但这并不是一个公平的比较;)

标签: java uuid


【解决方案1】:

这是在 beta 127 中运行的测试。

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

场景:

  • 一百万次调用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 ) + " )" );

以及定义每个线程的类……

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 微秒都是可以接受的。

【讨论】:

  • 呃,如果我错了,请纠正我,但你的数学是 nanos / 1000,这是 micro 秒,不是 milli 秒。
  • 您是否在任何地方使用变量 uuid?如果你不使用它,你可能什么也没做,因为当 JVM 检测到一个变量没有被使用时,它会优化代码。看看 Scott Oaks 的 Java Performance。您可能需要声明一个 volatile 字段,以确保基准衡量任何有用的东西。
【解决方案2】:

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

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

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

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

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

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

我不确定这种 hack 在实践中有多安全。

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

【讨论】:

  • 我认为我们也遇到了“缺乏熵”的问题。 UUID 生成开始很快,但几个小时后可能会爬行。在无头 VM 上运行可能会导致问题,因为操作系统的熵源较少。请参阅stackoverflow.com/questions/137212/… 了解更多信息。
  • 用户 Basil Bourque 的回答包括一个基准测试,该基准显示在一个基准测试期间每个 UUID 平均 20 us,其中 3 个线程争用每个线程尽可能快地生成 UUID。你认为这个熵率限制是否会影响这样的基准,如果它运行很长一段时间,每个 UUID 的平均 us 会在更长的测试运行时间段内更大?
  • 我不知道。尝试自己运行它...看看是否可以。
【解决方案3】:

我测试过

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

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

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

大约 80 毫秒

【讨论】:

  • 这些数字没有通过气味测试。 1110 毫秒生成 100 万个随机 UUID 听起来很快。但是 1110 毫秒生成 1 个 UUID 听起来太慢了。
  • 在我看来这两种方法的速度差不多,但它是 Windows 机器。
  • 它使用 SecureRandom 是有原因的,你不应该“使用常规 java.util.Random”,也不要期望 UUID 冲突。
  • @MikkelLøkke 我不确定你的推理是否正确。我认为最大的问题是可预测性,不是一致性。 UUID 可能用于安全目的——作为不可猜测的密钥——这需要高质量的熵。但在许多情况下,如果目标是不安全地避免碰撞,我相信random.nextLong() 策略就足够了。
  • @MikkelLøkke uuid 的“暴力破解”如何?考虑连续 30 年每秒 10 亿次大约是 2^60,它小于 uuid 空间的平方根,但比当时整个地球可能产生的预期要多许多数量级,但肯定在一个应用程序中。您能否详细说明一下您如何看待 4 型 uuid 被粗暴地暴力破解?
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

    使用版本 1 而不是 4

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

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

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

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

    JUG 库

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

    测试结果:20x

    阅读 2010 年文章 More on Java UUID Generator (JUG), a word on performance 中关于使用 Java UUID Generator 第 3 版的测试结果的性能讨论。 Tatu Saloranta 在他的 MacBook 上测试了各种 UUID。

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • 你确定 JVM 没有删除 UUID.randomUUID(); 行,因为它的结果没有被使用?
      【解决方案7】:

      我和其他人做了同样的测试,我的结果更像是每个 UUID 生成 300 纳秒。结果在 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
      

      【讨论】:

        猜你喜欢
        • 2021-04-16
        • 2021-06-24
        • 1970-01-01
        • 1970-01-01
        • 2013-04-16
        • 2017-06-25
        • 1970-01-01
        • 2015-03-27
        • 1970-01-01
        相关资源
        最近更新 更多