Відповідь 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.