Thật vậy, câu trả lời của broalid khá lắt léo - thông minh một cách ấn tượng, thực sự ... tuân thủ rfc4122, hơi dễ đọc và nhỏ gọn. Tuyệt vời!
Nhưng nếu bạn đang nhìn vào mà biểu hiện thường xuyên, những người nhiều replace()
callbacks, toString()
's và Math.random()
gọi hàm (nơi anh ta chỉ sử dụng 4 bit của kết quả và lãng phí phần còn lại), bạn có thể bắt đầu tự hỏi về hiệu suất. Thật vậy, joelpt thậm chí đã quyết định tung ra RFC cho tốc độ GUID chung vớigenerateQuickGUID
.
Nhưng, chúng ta có thể đạt được tốc độ và tuân thủ RFC không? Tôi nói "có! Chúng ta có thể duy trì khả năng đọc? Chà ... Không hẳn, nhưng thật dễ nếu bạn làm theo.
Nhưng trước tiên, kết quả của tôi, so với broofa, guid
(câu trả lời được chấp nhận) và không tuân thủ 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.
Vì vậy, bằng lặp thứ 6 của tôi về tối ưu hóa, tôi đánh bại câu trả lời phổ biến nhất hơn 12X , câu trả lời được chấp nhận bởi hơn 9X , và câu trả lời nhanh không tuân thủ bởi 2-3X . Và tôi vẫn tuân thủ rfc4122.
Quan tâm đến làm thế nào? Tôi đã đặt nguồn đầy đủ trên http://jsfiddle.net/jcward/7hyaC/3/ và trên http://jsperf.com/uuid-generator-opt/4
Để giải thích, hãy bắt đầu với mã của 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())
Vì vậy, nó thay thế x
bằng bất kỳ chữ số hex ngẫu nhiên nào, y
bằng dữ liệu ngẫu nhiên (ngoại trừ việc buộc 2 bit 10
trên cùng theo thông số RFC) và regex không khớp với -
hoặc4
ký tự, vì vậy anh ta không phải xử lý chúng. Rất, rất lắt léo.
Điều đầu tiên cần biết là các lệnh gọi hàm rất đắt, cũng như các biểu thức thông thường (mặc dù anh ta chỉ sử dụng 1, nhưng nó có 32 cuộc gọi lại, một cuộc gọi cho mỗi trận đấu và trong mỗi 32 cuộc gọi lại, nó gọi Math.random () và v. toString (16)).
Bước đầu tiên hướng tới hiệu suất là loại bỏ RegEx và các hàm gọi lại của nó và sử dụng một vòng lặp đơn giản để thay thế. Điều này có nghĩa là chúng ta phải đối phó với -
và các 4
nhân vật trong khi broalid thì không. Ngoài ra, lưu ý rằng chúng ta có thể sử dụng lập chỉ mục Chuỗi Array để giữ kiến trúc mẫu Chuỗi trơn bóng của mình:
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())
Về cơ bản, logic bên trong tương tự, ngoại trừ chúng tôi kiểm tra -
hoặc 4
và sử dụng vòng lặp while (thay vì replace()
gọi lại) giúp chúng tôi cải thiện gần gấp 3 lần!
Bước tiếp theo là một bước nhỏ trên máy tính để bàn nhưng tạo ra sự khác biệt khá lớn trên thiết bị di động. Chúng ta hãy thực hiện ít cuộc gọi Math.random () hơn và sử dụng tất cả các bit ngẫu nhiên đó thay vì ném 87% trong số chúng với một bộ đệm ngẫu nhiên được chuyển ra mỗi lần lặp. Chúng ta cũng hãy di chuyển định nghĩa mẫu đó ra khỏi vòng lặp, chỉ trong trường hợp nó giúp:
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())
Điều này giúp chúng tôi tiết kiệm 10-30% tùy thuộc vào nền tảng. Không tệ. Nhưng bước tiến lớn tiếp theo sẽ loại bỏ chức năng gọi toString hoàn toàn với một cổ điển tối ưu hóa - bảng tra cứu. Bảng tra cứu 16 phần tử đơn giản sẽ thực hiện công việc của toString (16) trong thời gian ngắn hơn nhiều:
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())
Tối ưu hóa tiếp theo là một cổ điển khác. Vì chúng ta chỉ xử lý 4 bit đầu ra trong mỗi lần lặp lặp, nên hãy cắt một nửa số vòng lặp và xử lý 8 bit mỗi lần lặp. Điều này thật khó khăn vì chúng ta vẫn phải xử lý các vị trí bit tuân thủ RFC, nhưng nó không quá khó. Sau đó, chúng tôi phải tạo một bảng tra cứu lớn hơn (16x16 hoặc 256) để lưu trữ 0x00 - 0xff và chúng tôi chỉ xây dựng nó một lần, bên ngoài hàm 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())
Tôi đã thử một e6 () xử lý 16 bit cùng một lúc, vẫn sử dụng LUT 256 phần tử và nó cho thấy lợi nhuận tối ưu hóa giảm dần. Mặc dù nó có số lần lặp ít hơn, logic bên trong rất phức tạp do quá trình xử lý tăng lên và nó thực hiện tương tự trên máy tính để bàn và chỉ nhanh hơn ~ 10% trên thiết bị di động.
Kỹ thuật tối ưu hóa cuối cùng để áp dụng - bỏ vòng lặp. Vì chúng tôi lặp một số lần cố định, về mặt kỹ thuật, chúng tôi có thể viết tất cả những điều này bằng tay. Tôi đã thử điều này một lần với một biến ngẫu nhiên r duy nhất mà tôi tiếp tục gán lại và hiệu suất giảm dần. Nhưng với bốn biến được gán dữ liệu ngẫu nhiên ở phía trước, sau đó sử dụng bảng tra cứu và áp dụng các bit RFC thích hợp, phiên bản này hút tất cả:
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())
Đã sửa đổi: http://jcward.com/UUID.js -UUID.generate()
Điều buồn cười là, tạo ra 16 byte dữ liệu ngẫu nhiên là phần dễ dàng. Toàn bộ thủ thuật là thể hiện nó ở định dạng Chuỗi với tuân thủ RFC và nó được thực hiện chặt chẽ nhất với 16 byte dữ liệu ngẫu nhiên, một vòng lặp và bảng tra cứu không được kiểm soát.
Tôi hy vọng logic của tôi là chính xác - rất dễ phạm sai lầm trong loại công việc tẻ nhạt này. Nhưng đầu ra có vẻ tốt với tôi. Tôi hy vọng bạn thích chuyến đi điên rồ này thông qua tối ưu hóa mã!
Được thông báo: mục tiêu chính của tôi là thể hiện và dạy các chiến lược tối ưu hóa tiềm năng. Các câu trả lời khác bao gồm các chủ đề quan trọng như va chạm và các số thực sự ngẫu nhiên, rất quan trọng để tạo ra các UUID tốt.