broofa's answer 非常漂亮,确实 - 非常聪明,真的... 符合 RFC4122,可读性强且紧凑。太棒了!
但是,如果您正在查看那个正则表达式,那些许多 replace() 回调、toString() 和 Math.random() 函数调用(他只使用了四位结果并浪费了其余的),您可能开始怀疑性能。事实上,joelpt 甚至决定放弃使用generateQuickGUID 的通用 GUID 速度的 RFC。
但是,我们能否获得速度和 RFC 合规性?我说,是的!我们可以保持可读性吗?嗯......不是真的,但如果你跟着它很容易。
但首先,我的结果与 broofa、guid(已接受的答案)和不符合 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.
所以在我的第 6 次优化迭代中,我击败了最受欢迎的答案超过 12 次strong>,超过了接受的答案 9 次,以及快速非2-3 次。而且我仍然符合 RFC 4122。
对如何感兴趣?我已将完整的源代码放在 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(除了根据RFC规范强制最高两位为10),并且正则表达式不匹配-或4 字符,所以他不必处理它们。非常非常流畅。
首先要知道的是函数调用很昂贵,正则表达式也是如此(虽然他只使用了 1 个,但它有 32 个回调,每个匹配一个,并且在 32 个回调中的每一个中它调用 Math.random()和 v.toString(16))。
实现性能的第一步是消除 RegEx 及其回调函数,并改用简单的循环。这意味着我们必须处理 - 和 4 字符,而 broofa 没有。另外,请注意,我们可以使用字符串数组索引来保持他光滑的字符串模板架构:
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,并使用while 循环(而不是replace() 回调)使我们提高了近3 倍!
下一步是桌面上的一个小步骤,但在移动设备上会有很大的不同。让我们进行更少的 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())
我尝试了一次处理 16 位的 e6(),仍然使用 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 字节的随机数据是很容易的部分。整个技巧是用符合 RFC 的 string 格式来表达它,并且最紧密地使用 16 字节的随机数据、展开的循环和查找表来完成。
我希望我的逻辑是正确的——在这种繁琐的工作中很容易出错。但输出对我来说看起来不错。我希望您通过代码优化享受这段疯狂的旅程!
请注意:我的主要目标是展示和教授潜在的优化策略。其他答案涵盖了重要主题,例如冲突和真正的随机数,这对于生成良好的 UUID 很重要。