J'ai une application Java basée sur le Web qui génère des UUID aléatoires pour les informations de session. L'un de nos testeurs réclame jusqu'à 350 ms pour générer des UUID basés sur son propre profilage, mais je n'ai pas encore été en mesure de reproduire ses résultats. Il pointe vers cet article http://www.cowtowncoder.com/blog/archives/2010/10/entry_429.html pour l'aider à sauvegarder ses résultats. Je voulais savoir si quelqu'un d'autre avait rencontré cette limitation avec la capacité de génération d'UUID intégrée de Java dans les applications Java 6 ou Java 7.
Je l'ai testé
for (;;) {
long t0 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
UUID.randomUUID();
}
System.out.println(System.currentTimeMillis() - t0);
}
sur mon PC, il est environ 1100 ms, ce qui est assez lent. UUID.randomUUID () utilise SecureRandom en interne. Pour accélérer le processus, nous pouvons utiliser Java.util.Random standard.
Random r = new Random();
for (;;) {
..
new UUID(r.nextLong(), r.nextLong());
c'est ~ 80 ms
Voici un test en beta 127.
Gardez à l'esprit que ce test est irréaliste, au-delà de tout scénario pessimiste imaginable. Mon but était de faire taire ceux qui utilisent mal la bouche uuid _ s) sans disposer des faits pour appuyer leurs critiques.
Scénario:
Java.util.UUID.randomUUID()
Exécution d'une boucle dans un thread, donc pas de conflit sur les méthodes/classes synchronisées.
// 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 ) + " )" );
À propos de 2 microsecondes par UUID.
Semblable à ce qui précède, mais en faisant une boucle d'un million d'appels, nous avons deux autres threads en cours d'exécution, chacun effectuant dix millions d'appels.
// 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 ) + " )" );
Et la classe définissant chaque fil…
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();
}
}
}
Environ 20 microsecondes par UUID.
Les exécutions ont été de 14, 20, 20, 23 et 24 microsecondes par UUID (pas dans cet ordre). Par conséquent, la controverse extrême n’était que 10 fois pire, 20 microsecondes étant acceptables dans toutes les utilisations du monde réel que je connaisse.
La forme aléatoire d'UUID nécessite une source de nombres aléatoires de "force de cryptographie". (Si ce n'était pas le cas, il est possible que la réémission d'un UUID donné puisse atteindre des niveaux inquiétants.)
Les générateurs de nombres aléatoires à force de cryptage typique utilisent une source d'entropie externe à l'application. Il peut s’agir d’un générateur de nombre aléatoire de matériel, mais le plus souvent c’est le «caractère aléatoire» accumulé qui est récolté par le système d’exploitation en fonctionnement normal. Le problème est que les sources d'entropie ont une limite de débit. Si vous dépassez ce taux sur une période donnée, vous pouvez drainer la source. Ce qui se passe ensuite dépend du système, mais sur certains systèmes, l’appel système à lire entropie s’arrêtera ... jusqu’à ce qu’il en reste plus.
Je suppose que c'est ce qui se passe sur le système de votre client.
Une solution de contournement (pour les systèmes Linux) consiste à installer le démon rngd
et à le configurer pour "compléter" le pool d'entropie à l'aide d'un générateur de nombres pseudo-aléatoires. L'inconvénient est que cela pourrait compromettre le caractère aléatoire de votre générateur UUID.
Le nombre de threads a un impact énorme sur les performances de la génération d'UUID. Ceci peut être expliqué en regardant l'implémentation de SecureRandom#nextBytes(byte[]
qui génère les nombres aléatoires pour UUID.randomUUID()
:
synchronized public void nextBytes(byte[] bytes) {
secureRandomSpi.engineNextBytes(bytes);
}
nextBytes
est synchronized
, ce qui entraîne une perte de performances significative en cas d'accès par différents threads.
Pourquoi ne pas utiliser le type d'UUID de la version 1?
Version 1 est basé sur adresse MAC et l'heure actuelle ("espace et temps"). Beaucoup moins susceptibles d'avoir des collisions que la version 4.
Version 4 est basé sur le fait qu’il est entièrement généré à partir de nombres aléatoires à l’aide d’un générateur aléatoire hautement cryptographique.
La machine virtuelle Java Oracle ne fournit pas de générateur de version 1, apparemment pour des raisons de sécurité et de confidentialité. La JVM ne permet pas d'accéder à l'adresse MAC de la machine hôte.
Il existe au moins une bibliothèque tierce disponible qui fournit des UUID de version 1, ainsi que d'autres versions: JUG - Java UUID Generator . Ils disent que les fonctionnalités introduites dans Java 6 leur permettent d’avoir accès à l’adresse MAC.
Lisez une discussion sur les performances avec les résultats de test à l'aide de Java UUID Generator version 3 dans l'article de 2010, Informations complémentaires sur Java UUID Generator (JUG), un mot sur les performances . Tatu Saloranta a testé divers types d’UUID sur son MacBook.
Photo de plus: La version MAC + Time est 20 fois plus rapide que la version aléatoire.
La variante basée sur le temps (adresse Ethernet et horodatage) est beaucoup plus rapide - presque 20 fois plus rapide que la variante aléatoire variante - générant environ 5 millions d'UUID par seconde.
Un test junit exécuté sous 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));
}
}
Et les résultats sur mon ordinateur portable i5 étaient les suivants:
-------------------------------------------------------
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 ms par invocation .
J'ai fait le même test que les autres et mes résultats sont plus proches de 300 NANOsecondes par génération d'UUID . Les résultats sont sur un PC WIN7 64 quad-core i7. J'ai essayé avec un jdk1.7.0_67 et avec un jdk1.8.0_40 JVM 64 bits.
Je suis un peu perplexe, mes résultats sont si différents de tous les autres ... Mais 1 ms pour générer un nombre aléatoire semblait BEAUCOUP!
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);
}
Le résultat :
UUIDs : 1 000 000
Total time (ns) : 320 715 288
Time per UUID (ns) : 320
5372630452959404665