la risposta di broofa è piuttosto semplice, davvero - straordinariamente intelligente, davvero ... conforme a rfc4122, in qualche modo leggibile e compatta. Eccezionale!
Ma se stai guardando quell'espressione regolare, quei tanti replace()
callback, toString()
e le Math.random()
chiamate di funzione (dove sta usando solo 4 bit del risultato e sprecando il resto), potresti iniziare a chiederti delle prestazioni. In effetti, Joelpt ha persino deciso di lanciare RFC per la velocità GUID generica con generateQuickGUID
.
Ma possiamo ottenere velocità e conformità RFC? Io dico si! Possiamo mantenere la leggibilità? Beh ... Non proprio, ma è facile se lo segui.
Ma prima, i miei risultati, rispetto a broofa, guid
(la risposta accettata), e non conformi a rfc 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.
Quindi, con la mia sesta iterazione di ottimizzazioni, ho battuto la risposta più popolare di oltre 12X , la risposta accettata di oltre 9X e la risposta rapida e non conforme di 2-3X . E sono ancora conforme a rfc4122.
Interessato a come? Ho messo la fonte completa su http://jsfiddle.net/jcward/7hyaC/3/ e su http://jsperf.com/uuid-generator-opt/4
Per una spiegazione, iniziamo con il codice di 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())
Quindi sostituisce x
con qualsiasi cifra esadecimale casuale, y
con dati casuali (tranne forzando i primi 2 bit 10
secondo le specifiche RFC), e il regex non corrisponde ai caratteri -
o 4
, quindi non deve affrontarli. Molto, molto lucido.
La prima cosa da sapere è che le chiamate di funzione sono costose, così come le espressioni regolari (sebbene usi solo 1, ha 32 callback, una per ogni corrispondenza, e in ognuna delle 32 callback chiama Math.random () e v. toString (16)).
Il primo passo verso le prestazioni è eliminare RegEx e le sue funzioni di callback e utilizzare invece un semplice loop. Questo significa che dobbiamo fare i conti con i personaggi -
e 4
mentre Broofa no. Inoltre, tieni presente che possiamo utilizzare l'indicizzazione String Array per mantenere la sua elegante architettura del modello String:
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())
Fondamentalmente, la stessa logica interna, tranne che controlliamo -
o 4
, e usando un ciclo while (invece di replace()
callbacks) ci dà un miglioramento quasi 3X!
Il passo successivo è piccolo sul desktop ma fa una discreta differenza sui dispositivi mobili. Facciamo meno chiamate Math.random () e utilizziamo tutti quei bit casuali invece di buttarne via l'87% con un buffer casuale che viene spostato fuori da ogni iterazione. Spostiamo anche quella definizione del modello fuori dal ciclo, nel caso in cui aiuti:
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())
Questo ci consente di risparmiare il 10-30% a seconda della piattaforma. Non male. Ma il prossimo grande passo si sbarazza delle chiamate alla funzione toString con un classico di ottimizzazione: la tabella di ricerca. Una semplice tabella di ricerca a 16 elementi eseguirà il lavoro di toString (16) in molto meno tempo:
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 prossima ottimizzazione è un altro classico. Dato che gestiamo solo 4 bit di output in ogni iterazione di loop, riduciamo il numero di loop a metà ed elaboriamo 8 bit per ogni iterazione. Questo è difficile poiché dobbiamo ancora gestire le posizioni dei bit conformi a RFC, ma non è troppo difficile. Dobbiamo quindi creare una tabella di ricerca più grande (16x16 o 256) per memorizzare 0x00 - 0xff e la costruiamo una sola volta, al di fuori della funzione 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())
Ho provato un e6 () che elabora 16 bit alla volta, usando ancora il LUT a 256 elementi e ha mostrato i rendimenti decrescenti dell'ottimizzazione. Sebbene avesse meno iterazioni, la logica interna era complicata dall'aumento dell'elaborazione e ha funzionato allo stesso modo sul desktop e solo del 10% più veloce sui dispositivi mobili.
La tecnica di ottimizzazione finale da applicare: srotolare il loop. Dato che eseguiamo il looping di un numero fisso di volte, possiamo tecnicamente scrivere tutto questo a mano. Ci ho provato una volta con una singola variabile casuale r che ho continuato a riassegnare e le prestazioni sono aumentate. Ma con quattro variabili assegnate in anticipo dati casuali, quindi utilizzando la tabella di ricerca e applicando i bit RFC corretti, questa versione li fuma tutti:
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())
Modificato: http://jcward.com/UUID.js -UUID.generate()
La cosa divertente è che generare 16 byte di dati casuali è la parte facile. L'intero trucco è esprimerlo in formato String con conformità RFC ed è realizzato in modo molto preciso con 16 byte di dati casuali, un ciclo non srotolato e una tabella di ricerca.
Spero che la mia logica sia corretta - è molto facile commettere un errore in questo tipo di noioso bit-lavoro. Ma le uscite sembrano buone per me. Spero che ti sia piaciuta questa folle corsa attraverso l'ottimizzazione del codice!
Attenzione: il mio obiettivo principale era mostrare e insegnare le potenziali strategie di ottimizzazione. Altre risposte riguardano argomenti importanti come collisioni e numeri veramente casuali, che sono importanti per generare buoni UUID.