Подтвердить что ты не робот

Производительность генератора случайных 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.

4b9b3361

Ответ 1

Я протестировал его

    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());

it ~ 80 мс

Ответ 2

Вот тестовый прогон в бета127.

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

Сценарий:

  • Тесная петля из миллиона звонков в java.util.UUID.randomUUID()
    • Один тест только с этим. (без споров)
    • Один тест с конкуренцией, когда два других потока находятся в тесном цикле, совершая десять миллионов вызовов.
  • Java 8 бета 127
    • Java-версия "1.8.0"
    • Java (TM) SE Runtime Environment (сборка 1.8.0-b127)
    • Java HotSpot (TM) 64-разрядная серверная виртуальная машина (сборка 25.0-b69, смешанный режим)
  • Запуск из среды IDE Netbeans 7.4
  • Выполнение внутри виртуальной машины
  • 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 ) + " )" );

Результаты

Около 2 микросекунды на UUID.

С утверждением

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

// 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();
        }

    }
}

Результаты

Около 20 микросекунд на UUID.

Запуски составляли 14, 20, 20, 23 и 24 микросекунды на UUID (не в этом порядке). Таким образом, в условиях крайней конкуренции было только примерно в 10 раз хуже, с 20 микросекундами, приемлемыми для любого реального использования, которое я знал.

Ответ 3

Случайная форма UUID обычно использует источник случайных чисел "стойкость криптографии".

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

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

Я ожидаю, что это то, что происходит в вашей клиентской системе. (Это не редкость на виртуальных машинах...)

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

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

Я не уверен, насколько безопасным этот взлом будет на практике.

Вот еще один вопрос на тему медленной генерации случайных чисел:

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Библиотека JUG

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

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

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

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

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

Ответ 6

Прогон 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 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 мс за вызов.

Ответ 7

Я сделал тот же тест, что и другие, и мои результаты больше похожи на 300 NANOseconds на создание UUID. Результаты находятся на i7 четырехъядерном процессоре WIN7 64. Я пробовал с jdk1.7.0_67 и с jvk1.8.0_40 64-битными JVM.

Я немного озадачен, мои результаты настолько отличаются от всех остальных... Но 1 мс для генерации случайного числа выглядел LOT!

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