Производительность случайной генерации UUID с Java 7 или Java 6

У меня есть веб-приложение Java, которое генерирует случайные UUID для информации о сеансе. Один из наших тестеров утверждает, что до 350 мс генерируют UUID на основе его собственного профилирования, но я еще не смог воспроизвести его результаты. Он указывает на эту статью http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html чтобы помочь создать резервную копию его результатов. Я хотел посмотреть, столкнулся ли кто-нибудь еще с этим ограничением со встроенной возможностью генерации UUID Java в приложения Java 6 или Java 7.

7 ответов


Я проверял

    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.утиль.Random

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

это ~80 мс


вот тестовый запуск в бета-127.

имейте в виду, что этот тест нереально, за пределами любого наихудшего сценария, который я могу себе представить. Моя цель состояла в том, чтобы успокоить тех, кто плохо использует UUIDS без фактов, чтобы подкрепить свою критику.

сценарий:

  • плотный цикл из миллиона вызовов 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, смешанный режим)
  • запуск из IDE Netbeans 7.4
  • выполнение внутри виртуального машина
  • Mac mini (конец 2012)
    • Маверикс
    • Intel i7 четырехъядерный процессор с HyperThreading (8 видимых ядер)
    • 16 гигов память!--22-->

Без Конкуренции

запуск одного цикла в одном потоке, поэтому нет разногласий по синхронизированным методам / классам.

// 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 ) + " )" );

результаты

о 2 микросекунд per UUID.

С Заявлением

подобно выше, но при выполнении цикла из миллиона вызовов у нас есть два других потока, где каждый делает десять


случайная форма UUID требует источника случайных чисел "криптографической силы". (Если это не так, то может быть вероятность того, что данный UUID будет переиздан, может увеличиться до тревожных уровней.)

типичные генераторы случайных чисел крипто-силы используют источник энтропии, который является внешним для приложения. Это может быть аппаратный генератор случайных чисел, но чаще всего это накопленная "случайность", которая собирается операционной системой в обычном режиме операция. Проблема в том, что источники энтропии имеют предел скорости. Если вы превысите эту скорость в течение определенного периода времени, вы можете слить источник. То, что происходит дальше, зависит от системы, но в некоторых системах syscall для чтения энтропии остановится ... пока не будет доступно больше.

Я ожидаю, что то, что происходит в системе клиента.

один обходной путь (для систем Linux) - установить rngd демон и настройте его для "пополнения" пула энтропии с помощью генератор псевдослучайных чисел. Недостатком является то, что это может поставить под угрозу случайность вашего генератора UUID.


количество потоков оказывает огромное влияние на производительность генерации UUIDs. Это можно объяснить, посмотрев на реализацию SecureRandom#nextBytes(byte[], который генерирует случайные числа для UUID.randomUUID():

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

nextBytes is synchronized что приводит к значительной потере производительности при доступе к различным потокам.


используйте версию 1 вместо 4

Как насчет использования типа UUID версии 1?

Вариант 1 на основе MAC-адрес и текущее время ("пространство и время"). Гораздо менее вероятны столкновения, чем версия 4.

Вариант 4 основан на полностью генерируется из случайных чисел с помощью криптографически сильного генератора случайных чисел.

Oracle JVM не предоставляет генератор версии 1, очевидно, из соображений безопасности и конфиденциальности. JVM не предоставляет доступ к MAC-адресу хост-машины.

кувшин библиотека

существует по крайней мере одна сторонняя библиотека, доступная doe, предоставляющая UUID версии 1, а также другие версии: JUG-Java UUID генератор. Они говорят, что функции, введенные в Java 6, позволяют им получить доступ к MAC-адресу.

результаты теста: 20x

читать обсуждение работы с тестом результаты использования Java UUID генератор версии 3 в 2010 году в статье подробнее о Java UUID Generator (JUG), слово о производительности. Тату Салоранта протестировал различные типы UUIDs на своем MacBook.

Upshot: версия MAC + Time в 20 раз быстрее, чем случайная версия.

вариант на основе времени (адрес Ethernet плюс временная метка) намного быстрее - почти 20 раз быстрее, чем на основе случайных вариант по умолчанию-примерно 5 миллион UUIDs в секунду.


тестовый запуск junit под jdk 1.7.0_40:

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 наносекунд на поколение UUID. Результаты находятся на i7 quad-core WIN7 64 PC. Я попробовал с jdk1.7.0_67 и с jdk1.8.0_40 64 бит JVMs.

Я немного озадачен, мои результаты настолько отличаются от всех других... Но 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