Відповідь broofa - дуже чітка, справді - вражаюче розумна, справді ... rfc4122 сумісна, дещо читабельна та компактна. Дивовижно!
Але якщо ви дивитесь на цей регулярний вираз, ті безлічі replace()зворотних викликів, викликів toString()та Math.random()функцій (де він використовує лише 4 біти результату і витрачає решту), ви можете почати замислюватися про ефективність. Дійсно, joelpt навіть вирішив викинути RFC для загальної швидкості GUID за допомогою generateQuickGUID.
Але чи можемо ми отримати швидкість та відповідність RFC? Я кажу: ТАК! Чи можемо ми зберегти читабельність? Ну ... Не дуже, але легко, якщо ви будете слідувати далі.
По-перше, мої результати порівняно з брофою guid(прийнята відповідь) та невідповідним стандартам 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.
Тож за моєю шостою ітерацією оптимізацій я переміг найпопулярнішу відповідь понад 12X , прийняту відповідь понад 9X та швидку несумісну відповідь на 2-3X . І я все ще сумісний з rfc4122.
Цікавить, як? Я розмістив повне джерело на http://jsfiddle.net/jcward/7hyaC/3/ та на http://jsperf.com/uuid-generator-opt/4
Для пояснення почнемо з коду 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())
Таким чином, він замінює xбудь-яку випадкову шістнадцяткову цифру, yвипадковими даними (за винятком примушування 2-х верхніх біт 10на специфікацію RFC), і регулярний вираз не відповідає символам -або 4знакам, тому він не повинен з ними мати справу. Дуже, дуже струнка.
Перше, що потрібно знати, це те, що виклики функцій є дорогими, як і регулярні вирази (хоча він використовує лише 1, у нього є 32 зворотні виклики, по одному на кожен матч, і в кожному з 32 зворотних зворотних дзвінків він називає Math.random () і v. toString (16)).
Першим кроком до продуктивності є усунення RegEx та його функцій зворотного виклику та використання простого циклу. Це означає , що ми повинні мати справу з -і 4символів , тоді як broofa не зробив. Також зауважте, що ми можемо використовувати індексацію String Array для збереження його архітектурної архітектури шаблонів 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())
В основному, та сама внутрішня логіка, за винятком того, що ми перевіряємо -чи 4, і використовуємо цикл час (замість цього)replace() зворотних викликів) отримує нам майже поліпшення 3X!
Наступним кроком є невеликий на робочому столі, але це робить гідну різницю в мобільному. Давайте зробимо менше викликів Math.random () та використаємо всі ці випадкові біти, а не викидаючи 87% з них випадковим буфером, який зміщується з кожної ітерації. Давайте також перемістимо це визначення шаблону з циклу, про всяк випадок, якщо це допоможе:
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())
Це економить нам 10-30% залежно від платформи. Непогано. Але наступним великим кроком позбавляється функція ToString викликає взагалі класику оптимізації - таблицю пошуку. Проста таблиця пошуку 16 елементів виконує завдання toString (16) за набагато менший час:
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())
Наступна оптимізація - ще одна класика. Оскільки ми обробляємо лише 4-розрядний висновок у кожній ітерації циклу, давайте скоротимо кількість циклів навпіл та обробимо 8-бітну ітерацію. Це складно, оскільки нам ще належить обробляти бітові позиції, сумісні з RFC, але це не надто складно. Потім нам потрібно зробити більшу таблицю пошуку (16x16 або 256), щоб зберігати 0x00 - 0xff, і ми будуємо її лише один раз, поза функцією 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())
Я спробував e6 (), який обробляє 16 біт одночасно, все ще використовуючи 256-елементний LUT, і це показало зменшення віддачі від оптимізації. Хоча вона мала менше ітерацій, внутрішня логіка ускладнювалася посиленою обробкою, і вона виконувала те саме на робочому столі, і лише на 10% швидше в мобільному.
Заключна техніка оптимізації, яку потрібно застосувати - розкрутити цикл. Оскільки ми фіксуємо фіксовану кількість разів, ми можемо технічно все це записати вручну. Я спробував це один раз з однією випадковою змінною r, яку я продовжував перепризначати, і продуктивність була заповнена. Але з чотирма змінними, призначеними випадковими даними наперед, потім, використовуючи таблицю пошуку та застосовуючи належні біти RFC, ця версія виганяє їх усіх:
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())
Модалізовано: http://jcward.com/UUID.js -UUID.generate()
Найцікавіше, що генерувати 16 байт випадкових даних - це легка частина. Вся хитрість полягає в тому, щоб виразити його у String-форматі з дотриманням RFC, і це найтісніше виконання з 16-ти байтами випадкових даних, незакрученим циклом та таблицею пошуку.
Я сподіваюсь, що моя логіка правильна - дуже легко помилитися в такому виснажливому доробку. Але результати мені добре виглядають. Я сподіваюся, що вам сподобалася ця шалена їзда через оптимізацію коду!
Будьте в курсі: моя головна мета полягала в тому, щоб показати і навчити потенційні стратегії оптимізації. Інші відповіді стосуються важливих тем, таких як зіткнення та справді випадкові числа, які важливі для створення хороших UUID.