logo
Published on

如何创建GUID / UUID?

Authors
  • Name
    Twitter

在现代编程中,GUID(全局唯一标识符)和UUID(通用唯一标识符)被广泛应用于数据库、分布式系统、Web开发等领域。本文将探讨如何创建符合 RFC 4122 标准的GUID,并且在性能上有所提升。

初始代码

首先,让我们来看看 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())

此代码通过正则表达式和多个 replace() 回调来生成UUID,这种方法虽然巧妙但在性能上有较多改进空间。

性能优化

第一步:消除正则表达式

我们首先可以通过循环代替正则表达式和回调函数,这将大幅提高性能:

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 字符,同时减少了不必要的函数调用和正则表达式的应用。

第二步:减少 Math.random() 调用

进一步优化,我们可以减少 Math.random() 的调用次数,并利用产生的所有随机位:

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 函数调用:

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',
  ]
  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())

通过查找表,这种方法减少了 toString 调用,从而提高了性能。

第四步:处理更多位

通过处理 8 位而不是 4 位,可以将循环次数减半,并进一步利用查找表:

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())

这样的方法进一步减少了循环次数,提升了性能。

最终优化:展开循环

通过展开循环和查表,我们可以实现最高效的UUID生成:

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())

这种方法避免了所有循环的开销,进一步提高了性能。

结语

通过多个步骤的优化,我们成功地创建了一种性能优越且符合 RFC 4122 标准的UUID生成算法。如果你对代码优化感兴趣,这里的每一个步骤都值得细细回味和探讨。

更多具体实现,可以参考模块化版的实现:UUID.js - UUID.generate()

希望这些优化策略对您有所启发,谢谢阅读!