La réponse de broofa est assez lisse, en effet - incroyablement intelligent, vraiment ... conforme rfc4122, quelque peu lisible et compact. Impressionnant!
Mais si vous regardez cette expression régulière, ces nombreux replace()
rappels toString()
et Math.random()
appels de fonction (où il n'utilise que 4 bits du résultat et gaspille le reste), vous pouvez commencer à vous interroger sur les performances. En effet, joelpt a même décidé de lancer RFC pour la vitesse GUID générique avecgenerateQuickGUID
.
Mais, pouvons-nous obtenir la vitesse et la conformité RFC? Je dis oui! Pouvons-nous maintenir la lisibilité? Eh bien ... Pas vraiment, mais c'est facile si vous suivez.
Mais d'abord, mes résultats, par rapport à broofa, guid
(la réponse acceptée) et le non-rfc conforme generateQuickGuid
:
Desktop Android
broofa: 1617ms 12869ms
e1: 636ms 5778ms
e2: 606ms 4754ms
e3: 364ms 3003ms
e4: 329ms 2015ms
e5: 147ms 1156ms
e6: 146ms 1035ms
e7: 105ms 726ms
guid: 962ms 10762ms
generateQuickGuid: 292ms 2961ms
- Note: 500k iterations, results will vary by browser/cpu.
Donc, à ma 6e itération d'optimisations, j'ai battu la réponse la plus populaire de plus de 12X , la réponse acceptée de plus de 9X et la réponse rapide non conforme de 2-3X . Et je suis toujours conforme rfc4122.
Intéressé par comment? J'ai mis la source complète sur http://jsfiddle.net/jcward/7hyaC/3/ et sur http://jsperf.com/uuid-generator-opt/4
Pour une explication, commençons par le code de broofa:
function broofa() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
console.log(broofa())
Il remplace donc x
par n'importe quel chiffre hexadécimal aléatoire, y
avec des données aléatoires (sauf forcer les 2 premiers bits 10
selon la spécification RFC), et l'expression régulière ne correspond pas à la -
ou4
caractères , il n'a donc pas à les traiter. Très, très lisse.
La première chose à savoir est que les appels de fonction sont chers, tout comme les expressions régulières (bien qu'il n'utilise que 1, il a 32 rappels, un pour chaque correspondance, et dans chacun des 32 rappels, il appelle Math.random () et v. toString (16)).
La première étape vers les performances consiste à éliminer le RegEx et ses fonctions de rappel et à utiliser une boucle simple à la place. Cela signifie que nous devons gérer les caractères -
et 4
, contrairement à broofa. Notez également que nous pouvons utiliser l'indexation de tableau de chaînes pour conserver son architecture de modèle de chaîne lisse:
function e1() {
var u='',i=0;
while(i++<36) {
var c='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'[i-1],r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);
u+=(c=='-'||c=='4')?c:v.toString(16)
}
return u;
}
console.log(e1())
Fondamentalement, la même logique intérieure, sauf que nous vérifions -
ou 4
, et en utilisant une boucle while (au lieu dereplace()
rappels) nous apporte une amélioration de presque 3 fois!
La prochaine étape est une petite sur le bureau mais fait une différence décente sur mobile. Faisons moins d'appels Math.random () et utilisons tous ces bits aléatoires au lieu de jeter 87% d'entre eux avec un tampon aléatoire qui est décalé à chaque itération. Déplaçons également cette définition de modèle hors de la boucle, juste au cas où cela aiderait:
function e2() {
var u='',m='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx',i=0,rb=Math.random()*0xffffffff|0;
while(i++<36) {
var c=m[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
u+=(c=='-'||c=='4')?c:v.toString(16);rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
}
return u
}
console.log(e2())
Cela nous permet d'économiser 10-30% selon la plate-forme. Pas mal. Mais la prochaine grande étape se débarrasse des appels de la fonction toString avec un classique d'optimisation - la table de recherche. Une simple table de recherche à 16 éléments effectuera le travail de toString (16) en beaucoup moins de temps:
function e3() {
var h='0123456789abcdef';
var k='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
/* same as e4() below */
}
function e4() {
var h=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
var k=['x','x','x','x','x','x','x','x','-','x','x','x','x','-','4','x','x','x','-','y','x','x','x','-','x','x','x','x','x','x','x','x','x','x','x','x'];
var u='',i=0,rb=Math.random()*0xffffffff|0;
while(i++<36) {
var c=k[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
u+=(c=='-'||c=='4')?c:h[v];rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
}
return u
}
console.log(e4())
La prochaine optimisation est un autre classique. Étant donné que nous ne gérons que 4 bits de sortie à chaque itération de boucle, réduisons le nombre de boucles de moitié et traitons 8 bits à chaque itération. C'est délicat car nous devons encore gérer les positions de bits conformes à la RFC, mais ce n'est pas trop difficile. Nous devons ensuite créer une table de recherche plus grande (16x16 ou 256) pour stocker 0x00 - 0xff, et nous la construisons une seule fois, en dehors de la fonction e5 ().
var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e5() {
var k=['x','x','x','x','-','x','x','-','4','x','-','y','x','-','x','x','x','x','x','x'];
var u='',i=0,rb=Math.random()*0xffffffff|0;
while(i++<20) {
var c=k[i-1],r=rb&0xff,v=c=='x'?r:(c=='y'?(r&0x3f|0x80):(r&0xf|0x40));
u+=(c=='-')?c:lut[v];rb=i%4==0?Math.random()*0xffffffff|0:rb>>8
}
return u
}
console.log(e5())
J'ai essayé un e6 () qui traite 16 bits à la fois, en utilisant toujours la LUT à 256 éléments, et cela a montré les rendements décroissants de l'optimisation. Bien qu'il ait eu moins d'itérations, la logique interne a été compliquée par l'augmentation du traitement, et il a effectué la même chose sur le bureau, et seulement 10% plus rapide sur le mobile.
La dernière technique d'optimisation à appliquer - déroule la boucle. Puisque nous bouclons un nombre fixe de fois, nous pouvons techniquement tout écrire à la main. J'ai essayé cela une fois avec une seule variable aléatoire r que je continuais de réassigner, et les performances étaient optimisées. Mais avec quatre variables assignées à l'avance des données aléatoires, puis en utilisant la table de recherche et en appliquant les bits RFC appropriés, cette version les fume toutes:
var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e7()
{
var d0 = Math.random()*0xffffffff|0;
var d1 = Math.random()*0xffffffff|0;
var d2 = Math.random()*0xffffffff|0;
var d3 = Math.random()*0xffffffff|0;
return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+
lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+
lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
}
console.log(e7())
Modualisé: http://jcward.com/UUID.js -UUID.generate()
Le plus drôle, c'est que générer 16 octets de données aléatoires est la partie la plus facile. L'astuce consiste à l'exprimer au format String avec la conformité RFC, et c'est le plus étroitement accompli avec 16 octets de données aléatoires, une boucle déroulée et une table de recherche.
J'espère que ma logique est correcte - il est très facile de se tromper dans ce genre de travail fastidieux. Mais les sorties me semblent bonnes. J'espère que vous avez apprécié cette course folle grâce à l'optimisation du code!
Soyez avisé: mon objectif principal était de montrer et d'enseigner des stratégies d'optimisation potentielles. D'autres réponses couvrent des sujets importants tels que les collisions et les nombres vraiment aléatoires, qui sont importants pour générer de bons UUID.