Die Antwort von broofa ist in der Tat ziemlich raffiniert - beeindruckend clever, wirklich ... rfc4122-konform, etwas lesbar und kompakt. Genial!
Wenn Sie sich jedoch diesen regulären Ausdruck, diese vielen replace()
Rückrufe, Funktionsaufrufe toString()
und Math.random()
Funktionsaufrufe ansehen (bei denen er nur 4 Bits des Ergebnisses verwendet und den Rest verschwendet), werden Sie sich möglicherweise über die Leistung wundern. In der Tat hat Joelpt sogar beschlossen, RFC für die generische GUID-Geschwindigkeit mit wegzuwerfen generateQuickGUID
.
Aber können wir Geschwindigkeit und RFC-Konformität erreichen? Ich sage ja! Können wir die Lesbarkeit aufrechterhalten? Nun ... Nicht wirklich, aber es ist einfach, wenn Sie mitmachen.
Aber zuerst meine Ergebnisse im Vergleich zu Broofa guid
(die akzeptierte Antwort) und die nicht rfc-konformen 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.
Also von meiner sechsten Iteration von Optimierungen, schlug ich die populärste Antwort von über 12X , die akzeptierte Antwort von über 9X und die schnell nicht konforme Antwort von 2-3X . Und ich bin immer noch rfc4122-konform.
Interessiert wie? Ich habe die vollständige Quelle auf http://jsfiddle.net/jcward/7hyaC/3/ und auf http://jsperf.com/uuid-generator-opt/4 veröffentlicht
Beginnen wir zur Erklärung mit dem Code von 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())
Es wird also x
durch eine zufällige hexadezimale Ziffer y
mit zufälligen Daten ersetzt (außer dass die obersten 2 Bits 10
gemäß der RFC-Spezifikation erzwungen werden), und der reguläre Ausdruck stimmt nicht mit den Zeichen -
oder überein 4
, sodass er sich nicht mit ihnen befassen muss. Sehr, sehr glatt.
Das Erste, was man wissen muss, ist, dass Funktionsaufrufe teuer sind, ebenso wie reguläre Ausdrücke (obwohl er nur 1 verwendet, hat er 32 Rückrufe, einen für jede Übereinstimmung, und in jedem der 32 Rückrufe ruft er Math.random () und v auf. toString (16)).
Der erste Schritt in Richtung Leistung besteht darin, den RegEx und seine Rückruffunktionen zu entfernen und stattdessen eine einfache Schleife zu verwenden. Dies bedeutet, dass wir uns mit den Zeichen -
und befassen müssen, 4
während Broofa dies nicht tat. Beachten Sie auch, dass wir die String-Array-Indizierung verwenden können, um seine raffinierte String-Vorlagenarchitektur beizubehalten:
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())
Grundsätzlich führt dieselbe innere Logik, außer dass wir nach -
oder suchen 4
und eine while-Schleife (anstelle von replace()
Rückrufen) verwenden, zu einer fast dreifachen Verbesserung!
Der nächste Schritt ist ein kleiner auf dem Desktop, macht aber auf Mobilgeräten einen ordentlichen Unterschied. Lassen Sie uns weniger Math.random () -Aufrufe durchführen und all diese zufälligen Bits verwenden, anstatt 87% davon mit einem zufälligen Puffer wegzuwerfen, der bei jeder Iteration verschoben wird. Verschieben wir auch diese Vorlagendefinition aus der Schleife, nur für den Fall, dass es hilft:
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())
Dies spart uns je nach Plattform 10-30%. Nicht schlecht. Aber der nächste große Schritt beseitigt die Funktionsaufrufe von toString mit einem Optimierungsklassiker - der Nachschlagetabelle. Eine einfache Nachschlagetabelle mit 16 Elementen erledigt die Aufgabe von toString (16) in viel kürzerer Zeit:
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())
Die nächste Optimierung ist ein weiterer Klassiker. Da wir in jeder Schleifeniteration nur 4-Bit-Ausgabe verarbeiten, halbieren wir die Anzahl der Schleifen und verarbeiten jede Iteration 8-Bit. Dies ist schwierig, da wir immer noch mit den RFC-kompatiblen Bitpositionen umgehen müssen, aber es ist nicht zu schwierig. Wir müssen dann eine größere Nachschlagetabelle (16x16 oder 256) erstellen, um 0x00 - 0xff zu speichern, und wir erstellen sie nur einmal außerhalb der Funktion 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())
Ich habe ein e6 () ausprobiert, das jeweils 16 Bit verarbeitet und dabei immer noch die 256-Elemente-LUT verwendet, und es hat die abnehmenden Optimierungsrenditen gezeigt. Obwohl es weniger Iterationen gab, wurde die innere Logik durch die erhöhte Verarbeitung kompliziert, und es lief auf dem Desktop genauso und auf Mobilgeräten nur ~ 10% schneller.
Die letzte anzuwendende Optimierungstechnik - Rollen Sie die Schleife ab. Da wir eine feste Anzahl von Schleifen durchlaufen, können wir dies alles technisch von Hand ausschreiben. Ich habe dies einmal mit einer einzelnen Zufallsvariablen r versucht, die ich immer wieder neu zugewiesen habe, und die Leistung wurde gesteigert. Da jedoch vier Variablen im Voraus zufällige Daten zugewiesen wurden, dann die Nachschlagetabelle verwendet und die richtigen RFC-Bits angewendet wurden, raucht diese Version alle:
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())
Modifiziert: http://jcward.com/UUID.js -UUID.generate()
Das Lustige ist, 16 Bytes zufälliger Daten zu generieren, ist der einfache Teil. Der ganze Trick besteht darin, es im String-Format mit RFC-Konformität auszudrücken, und es wird am besten mit 16 Bytes zufälliger Daten, einer nicht gerollten Schleife und einer Nachschlagetabelle erreicht.
Ich hoffe, meine Logik ist richtig - es ist sehr leicht, bei dieser Art von mühsamer Arbeit einen Fehler zu machen. Aber die Ausgänge sehen für mich gut aus. Ich hoffe, Sie haben diese tolle Fahrt durch die Codeoptimierung genossen!
Seien Sie gewarnt: Mein primäres Ziel war es, mögliche Optimierungsstrategien aufzuzeigen und zu vermitteln. Andere Antworten behandeln wichtige Themen wie Kollisionen und echte Zufallszahlen, die für die Generierung guter UUIDs wichtig sind.