Tìm hiểu cách đo lường mức sử dụng bộ nhớ của trang web trong môi trường thực tế để phát hiện sự hồi quy.
Trình duyệt tự động quản lý bộ nhớ của các trang web. Bất cứ khi nào một trang web tạo một đối tượng, trình duyệt sẽ phân bổ một phần bộ nhớ "ngầm" để lưu trữ đối tượng đó. Vì bộ nhớ là tài nguyên có hạn, nên trình duyệt sẽ thực hiện thu gom rác để phát hiện thời điểm không cần đến đối tượng nữa và giải phóng phân đoạn bộ nhớ cơ bản.
Tuy nhiên, việc phát hiện không phải lúc nào cũng chính xác và Bài toán dừng của Alan Turing đã chứng minh rằng việc phát hiện chính xác là một nhiệm vụ bất khả thi. Do đó, các trình duyệt ước chừng khái niệm "cần có một đối tượng" bằng khái niệm "có thể truy cập được một đối tượng". Nếu trang web không thể truy cập vào một đối tượng thông qua các biến của đối tượng đó và các trường của những đối tượng có thể truy cập khác, thì trình duyệt có thể thu hồi đối tượng đó một cách an toàn. Sự khác biệt giữa hai khái niệm này dẫn đến rò rỉ bộ nhớ như minh hoạ trong ví dụ sau.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
Ở đây, mảng lớn hơn b không còn cần thiết nữa, nhưng trình duyệt không thu hồi mảng này vì vẫn có thể truy cập được thông qua object.b trong lệnh gọi lại. Do đó, bộ nhớ của mảng lớn hơn sẽ bị rò rỉ.
Rò rỉ bộ nhớ là hiện tượng phổ biến trên web, như có thể thấy trong nghiên cứu này. Bạn có thể dễ dàng tạo ra một rò rỉ bằng cách quên huỷ đăng ký trình nghe sự kiện, vô tình thu thập các đối tượng từ một iframe, không đóng một worker, tích luỹ các đối tượng trong mảng, v.v. Nếu một trang web bị rò rỉ bộ nhớ, thì mức sử dụng bộ nhớ của trang web đó sẽ tăng lên theo thời gian và trang web sẽ có vẻ chậm và cồng kềnh đối với người dùng.
Bước đầu tiên để giải quyết vấn đề này là đo lường vấn đề. performance.measureUserAgentSpecificMemory() API mới cho phép nhà phát triển đo lường mức sử dụng bộ nhớ của trang web trong môi trường thực tế, từ đó phát hiện lỗi rò rỉ bộ nhớ bị bỏ sót trong quá trình kiểm thử cục bộ.
performance.measureUserAgentSpecificMemory() khác với API performance.memory cũ như thế nào?
Nếu đã quen thuộc với API performance.memory không chuẩn hiện có,
bạn có thể thắc mắc về điểm khác biệt giữa API mới và API đó. Điểm khác biệt chính là API cũ trả về kích thước của vùng nhớ khối xếp JavaScript, trong khi API mới ước tính bộ nhớ mà trang web sử dụng. Sự khác biệt này trở nên quan trọng khi Chrome chia sẻ cùng một vùng nhớ khối xếp với nhiều trang web (hoặc nhiều phiên bản của cùng một trang web). Trong những trường hợp như vậy, kết quả của API cũ có thể bị sai lệch tuỳ ý. Vì API cũ được xác định theo các thuật ngữ dành riêng cho việc triển khai như "vùng nhớ khối xếp", nên việc chuẩn hoá API này là không thể.
Một điểm khác biệt nữa là API mới thực hiện đo lường bộ nhớ trong quá trình thu gom rác. Việc này giúp giảm độ nhiễu trong kết quả, nhưng có thể mất một lúc thì kết quả mới được tạo. Xin lưu ý rằng các trình duyệt khác có thể quyết định triển khai API mới mà không cần dựa vào tính năng dọn rác.
Trường hợp sử dụng đề xuất
Mức sử dụng bộ nhớ của một trang web phụ thuộc vào thời gian diễn ra sự kiện, hành động của người dùng và hoạt động thu gom rác. Đó là lý do tại sao API đo lường bộ nhớ được dùng để tổng hợp dữ liệu mức sử dụng bộ nhớ từ môi trường thực tế. Kết quả của từng cuộc gọi sẽ ít hữu ích hơn. Ví dụ về trường hợp sử dụng:
- Phát hiện hồi quy trong quá trình phát hành phiên bản mới của trang web để phát hiện các lỗi rò rỉ bộ nhớ mới.
- Thử nghiệm A/B một tính năng mới để đánh giá mức độ ảnh hưởng của tính năng đó đến bộ nhớ và phát hiện lỗi rò rỉ bộ nhớ.
- Tương quan giữa mức sử dụng bộ nhớ với thời lượng phiên để xác minh sự hiện diện hoặc vắng mặt của lỗi rò rỉ bộ nhớ.
- Liên kết mức sử dụng bộ nhớ với chỉ số người dùng để hiểu rõ tác động tổng thể của mức sử dụng bộ nhớ.
Khả năng tương thích với trình duyệt
Hiện tại, API này chỉ được hỗ trợ trong các trình duyệt dựa trên Chromium, bắt đầu từ Chrome 89. Kết quả của API phụ thuộc nhiều vào cách triển khai vì các trình duyệt có nhiều cách khác nhau để biểu thị đối tượng trong bộ nhớ và nhiều cách khác nhau để ước tính mức sử dụng bộ nhớ. Trình duyệt có thể loại trừ một số vùng bộ nhớ khỏi việc lưu giữ hồ sơ nếu việc lưu giữ hồ sơ đúng cách quá tốn kém hoặc không khả thi. Do đó, bạn không thể so sánh kết quả giữa các trình duyệt. Việc so sánh kết quả chỉ có ý nghĩa đối với cùng một trình duyệt.
Sử dụng performance.measureUserAgentSpecificMemory()
Phát hiện tính năng
Hàm performance.measureUserAgentSpecificMemory sẽ không hoạt động hoặc có thể không thành công kèm theo lỗi SecurityError nếu môi trường thực thi không đáp ứng các yêu cầu bảo mật để ngăn chặn rò rỉ thông tin giữa các nguồn gốc khác nhau.
API này dựa vào tính năng tách biệt nhiều nguồn gốc mà một trang web có thể kích hoạt bằng cách đặt tiêu đề COOP+COEP.
Bạn có thể phát hiện khả năng hỗ trợ trong thời gian chạy:
if (!window.crossOriginIsolated) {
console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
} else if (!performance.measureUserAgentSpecificMemory) {
console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
} else {
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('The context is not secure.');
} else {
throw error;
}
}
console.log(result);
}
Kiểm thử cục bộ
Chrome thực hiện đo lường bộ nhớ trong quá trình thu gom rác, tức là API không phân giải lời hứa kết quả ngay lập tức mà thay vào đó sẽ đợi lần thu gom rác tiếp theo.
Việc gọi API sẽ buộc thu gom rác sau một khoảng thời gian chờ, hiện được đặt thành 20 giây, mặc dù có thể xảy ra sớm hơn. Việc khởi động Chrome bằng cờ hiệu dòng lệnh --enable-blink-features='ForceEagerMeasureMemory' sẽ giảm thời gian chờ xuống 0 và rất hữu ích cho việc gỡ lỗi và kiểm thử cục bộ.
Ví dụ
Bạn nên sử dụng API này để xác định một trình giám sát bộ nhớ chung, lấy mẫu mức sử dụng bộ nhớ của toàn bộ trang web và gửi kết quả đến máy chủ để tổng hợp và phân tích. Cách đơn giản nhất là lấy mẫu định kỳ, ví dụ: cứ sau M phút. Tuy nhiên, điều này gây ra sai lệch cho dữ liệu vì mức sử dụng bộ nhớ cao nhất có thể xảy ra giữa các mẫu.
Ví dụ sau đây cho thấy cách thực hiện các phép đo bộ nhớ không thiên vị bằng cách sử dụng quy trình Poisson, đảm bảo rằng các mẫu có khả năng xảy ra như nhau tại bất kỳ thời điểm nào.
Trước tiên, hãy xác định một hàm lên lịch cho lần đo lường bộ nhớ tiếp theo bằng cách sử dụng setTimeout() với khoảng thời gian ngẫu nhiên.
function scheduleMeasurement() {
// Check measurement API is available.
if (!window.crossOriginIsolated) {
console.log('performance.measureUserAgentSpecificMemory() is only available in cross-origin-isolated pages');
console.log('See https://web.dev/coop-coep/ to learn more')
return;
}
if (!performance.measureUserAgentSpecificMemory) {
console.log('performance.measureUserAgentSpecificMemory() is not available in this browser');
return;
}
const interval = measurementInterval();
console.log(`Running next memory measurement in ${Math.round(interval / 1000)} seconds`);
setTimeout(performMeasurement, interval);
}
Hàm measurementInterval() tính toán một khoảng thời gian ngẫu nhiên tính bằng mili giây
sao cho trung bình cứ 5 phút lại có một phép đo. Hãy xem bài viết Phân phối mũ nếu bạn quan tâm đến toán học đằng sau hàm này.
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
Cuối cùng, hàm không đồng bộ performMeasurement() gọi API, ghi lại kết quả và lên lịch cho lần đo lường tiếp theo.
async function performMeasurement() {
// 1. Invoke performance.measureUserAgentSpecificMemory().
let result;
try {
result = await performance.measureUserAgentSpecificMemory();
} catch (error) {
if (error instanceof DOMException && error.name === 'SecurityError') {
console.log('The context is not secure.');
return;
}
// Rethrow other errors.
throw error;
}
// 2. Record the result.
console.log('Memory usage:', result);
// 3. Schedule the next measurement.
scheduleMeasurement();
}
Cuối cùng, hãy bắt đầu đo lường.
// Start measurements.
scheduleMeasurement();
Kết quả có thể như sau:
// Console output:
{
bytes: 60_100_000,
breakdown: [
{
bytes: 40_000_000,
attribution: [{
url: 'https://example.com/',
scope: 'Window',
}],
types: ['JavaScript']
},
{
bytes: 20_000_000,
attribution: [{
url: 'https://example.com/iframe',
container: {
id: 'iframe-id-attribute',
src: '/iframe',
},
scope: 'Window',
}],
types: ['JavaScript']
},
{
bytes: 100_000,
attribution: [],
types: ['DOM']
},
],
}
Mức sử dụng bộ nhớ tổng thể ước tính được trả về trong trường bytes. Giá trị này phụ thuộc nhiều vào cách triển khai và không thể so sánh giữa các trình duyệt. Thậm chí, hành vi này có thể thay đổi giữa các phiên bản khác nhau của cùng một trình duyệt. Giá trị này bao gồm bộ nhớ JavaScript và DOM của tất cả iframe, cửa sổ liên quan và trình chạy web trong quy trình hiện tại.
Danh sách breakdown cung cấp thêm thông tin về bộ nhớ đã sử dụng. Mỗi mục mô tả một phần bộ nhớ và gán phần bộ nhớ đó cho một tập hợp cửa sổ, iframe và worker được xác định bằng URL. Trường types liệt kê các loại bộ nhớ dành riêng cho quá trình triển khai liên kết với bộ nhớ.
Điều quan trọng là phải xử lý tất cả danh sách theo cách chung chung và không cố định giá trị trong mã
các giả định dựa trên một trình duyệt cụ thể. Ví dụ: một số trình duyệt có thể trả về breakdown trống hoặc attribution trống. Các trình duyệt khác có thể trả về nhiều mục trong attribution cho biết rằng các trình duyệt đó không thể phân biệt mục nào trong số này sở hữu bộ nhớ.
Phản hồi
Nhóm cộng đồng về hiệu suất web và nhóm Chrome rất mong được lắng nghe ý kiến và trải nghiệm của bạn về performance.measureUserAgentSpecificMemory().
Hãy cho chúng tôi biết về thiết kế API
Có điều gì về API không hoạt động như mong đợi không? Hay có thuộc tính nào còn thiếu mà bạn cần để triển khai ý tưởng của mình không? Gửi vấn đề về thông số kỹ thuật trên kho lưu trữ GitHub performance.measureUserAgentSpecificMemory() hoặc thêm ý kiến của bạn vào một vấn đề hiện có.
Báo cáo vấn đề về việc triển khai
Bạn có phát hiện lỗi trong quá trình triển khai của Chrome không? Hay cách triển khai
khác với thông số kỹ thuật? Gửi lỗi tại new.crbug.com. Hãy nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, cung cấp hướng dẫn đơn giản để tái hiện lỗi và đặt Thành phần thành Blink>PerformanceAPIs.
Thể hiện sự ủng hộ
Bạn có dự định sử dụng performance.measureUserAgentSpecificMemory() không? Sự ủng hộ công khai của bạn giúp nhóm Chrome ưu tiên các tính năng và cho các nhà cung cấp trình duyệt khác biết tầm quan trọng của việc hỗ trợ các tính năng đó. Hãy gửi một tweet đến @ChromiumDev và cho chúng tôi biết bạn đang sử dụng tính năng này ở đâu và như thế nào.
Đường liên kết hữu ích
- Người giải thích
- Lỗi theo dõi
- Mục ChromeStatus.com
- Thay đổi kể từ API Bản dùng thử theo nguyên gốc
- Bản dùng thử theo nguyên gốc đã kết thúc
Xác nhận
Xin chân thành cảm ơn Domenic Denicola, Yoav Weiss, Mathias Bynens đã xem xét thiết kế API, và Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz đã xem xét mã trong Chrome. Tôi cũng cảm ơn Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan và Neil Mckay đã cung cấp ý kiến phản hồi có giá trị của người dùng, giúp cải thiện đáng kể API.