Tìm hiểu cách đo lường mức sử dụng bộ nhớ của trang web trong quá trình sản xuất để phát hiện các trường hợp 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à một tài nguyên hữu hạn, nên trình duyệt thực hiện quy trình thu gom rác để phát hiện thời điểm không còn cần đến một đối tượng và giải phóng khối 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à Vấn đề dừng của Alan Turing đã chứng minh rằng việc phát hiện hoàn toàn chính xác là điều không thể. 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 vào 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 và trường của đối tượng có thể truy cập khác, thì trình duyệt có thể an toàn lấy lại đối tượng đó. Sự khác biệt giữa hai khái niệm này dẫn đến tình trạng 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 b lớn hơn không còn cần thiết nữa, nhưng trình duyệt không lấy lại mảng này vì mảng 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 bị rò rỉ.
Rò rỉ bộ nhớ là hiện tượng phổ biến trên Web, như bạn có thể thấy trong nghiên cứu này. Bạn có thể dễ dàng đưa một đối tượng vào bộ nhớ bằng cách quên huỷ đăng ký một trình nghe sự kiện, vô tình chụ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ẽ xuất hiện chậm và quá tải đối với người dùng.
Bước đầu tiên để giải quyết vấn đề này là đo lường. API performance.measureUserAgentSpecificMemory() mới cho phép nhà phát triển đo lường mức sử dụng bộ nhớ của các trang web trong quá trình sản xuất, từ đó phát hiện các lỗi rò rỉ bộ nhớ 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 theo tiêu 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 này. Đ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 dùng chung cùng một vùng nhớ heap 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ị lệch tuỳ ý. Vì API cũ được xác định theo các điều khoản dành riêng cho việc triển khai, chẳng hạn như "heap" (heap), nên việc chuẩn hoá API này là vô vọng.
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. Điều này giúp giảm độ nhiễu trong kết quả, nhưng có thể mất một thời gian cho đến khi kết quả đượ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 thu gom rác.
Các trường hợp sử dụng được đề xuất
Mức sử dụng bộ nhớ của một trang web phụ thuộc vào thời gian của các sự kiện, hành động của người dùng và hoạt động thu gom rác. Đó là lý do API đo lường bộ nhớ được dùng để tổng hợp dữ liệu mức sử dụng bộ nhớ từ quy trình sản xuất. Kết quả của từng cuộc gọi riêng lẻ sẽ ít hữu ích hơn. Ví dụ về các trường hợp sử dụng:
- Phát hiện hồi quy trong quá trình triển khai phiên bản mới của trang web để phát hiện các rò rỉ bộ nhớ mới.
- Thử nghiệm A/B một tính năng mới để đánh giá tác động của tính năng đó đến bộ nhớ và phát hiện rò rỉ bộ nhớ.
- Tương quan 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 không có lỗi rò rỉ bộ nhớ.
- Tương quan mức sử dụng bộ nhớ với các chỉ số về người dùng để nắm được 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 này phụ thuộc rất nhiều vào quá trình triển khai vì các trình duyệt có nhiều cách biểu thị đối tượng trong bộ nhớ và nhiều cách ước tính mức sử dụng bộ nhớ. Các trình duyệt có thể loại trừ một số vùng nhớ khỏi việc tính toán nếu việc tính toán thích hợp quá tốn kém hoặc không khả thi. Do đó, không thể so sánh kết quả trên các trình duyệt. Bạn chỉ nên so sánh kết quả cho cùng một trình duyệt.
Sử dụng performance.measureUserAgentSpecificMemory()
Phát hiện đối tượng
Hàm performance.measureUserAgentSpecificMemory sẽ không hoạt động hoặc có thể không thành công vớ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 khác nguồn gốc.
Tính năng 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 chế độ 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 thập rác, tức là API không giải quyết ngay lời hứa kết quả mà thay vào đó, API sẽ đợi quá trình thu thập rác tiếp theo.
Việc gọi API sẽ buộc quá trình thu gom rác diễn ra sau một khoảng thời gian chờ nhất định (hiện được đặt thành 20 giây, mặc dù có thể diễn ra sớm hơn). Việc khởi động Chrome bằng cờ dòng lệnh --enable-blink-features='ForceEagerMeasureMemory' sẽ giảm thời gian chờ xuống 0 và hữu ích cho việc gỡ lỗi và kiểm thử cục bộ.
Ví dụ
Cách sử dụng API được đề xuất là 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ột 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ỳ, chẳng hạn như cứ M phút một lần. Tuy nhiên, điều đó sẽ gây ra sai lệch cho dữ liệu vì các đỉnh bộ nhớ có thể xảy ra giữa các mẫu.
Ví dụ sau đây minh hoạ cách thực hiện các phép đo bộ nhớ không thiên vị bằng quy trình Poisson, đảm bảo rằng các mẫu có khả năng xuất hiện như nhau tại bất kỳ thời điểm nào (bản minh hoạ, nguồn).
Trước tiên, hãy xác định một hàm lên lịch đo lường bộ nhớ tiếp theo bằng cách sử dụng setTimeout() với một 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 sẽ có một phép đo. Hãy xem Phân phối theo hàm mũ nếu bạn quan tâm đến công thức 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 performMeasurement() không đồng bộ sẽ gọi API, ghi lại kết quả và lên lịch cho hoạt động đ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.
// 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']
},
],
}
Tổng mức sử dụng bộ nhớ ước tính được trả về trong trường bytes. Giá trị này phụ thuộc rất nhiều vào việc triển khai và không thể so sánh giữa các trình duyệt. Thậm chí, điều này có thể thay đổi giữa các phiên bản 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ác cửa sổ liên quan và trình chạy dịch vụ trên web trong quy trình hiện tại.
Danh sách breakdown cung cấp thêm thông tin về bộ nhớ đã dùng. Mỗi mục mô tả một phần bộ nhớ và gán phần đó cho một nhóm 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 được liên kết với bộ nhớ.
Điều quan trọng là phải xử lý tất cả các danh sách theo cách chung chung và không mã hoá cứng 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ề một breakdown trống hoặc một 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 chúng 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 trên web và nhóm Chrome rất mong nhận được ý kiến và trải nghiệm của bạn về performance.measureUserAgentSpecificMemory().
Cho chúng tôi biết về thiết kế API
Có vấn đề gì về API không hoạt động như mong đợi không? Hay có thuộc tính nào bị 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ữ performance.measureUserAgentSpecificMemory() trên GitHub 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 thấy lỗi trong quá trình triển khai của Chrome không? Hay việc triển khai có khác với quy cách không? Báo cáo lỗi tại new.crbug.com. Nhớ cung cấp càng nhiều thông tin chi tiết càng tốt, hướng dẫn đơn giản để mô phỏng lỗi và đặt Thành phần thành Blink>PerformanceAPIs.
Thể hiện sự ủng hộ
Bạn có định 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 thấy tầm quan trọng của việc hỗ trợ các tính năng này. Gửi một tweet đến @ChromiumDev và cho chúng tôi biết bạn đang sử dụng công cụ này ở đâu và như thế nào.
Đường liên kết hữu ích
- Video giải thích
- Bản minh hoạ | Nguồn minh hoạ
- Lỗi theo dõi
- Mục trên ChromeStatus.com
- Các thay đổi kể từ API Thử nghiệm ban đầu
- Bản dùng thử theo nguyên gốc đã kết thúc
Lời cảm ơn
Xin chân thành cảm ơn Domenic Denicola, Yoav Weiss, Mathias Bynens vì đã đánh giá thiết kế API, cũng như Dominik Inführ, Hannes Payer, Kentaro Hara, Michael Lippautz vì đã đánh giá mã trong Chrome. Tôi cũng xin cảm ơn Per Parker, Philipp Weis, Olga Belomestnykh, Matthew Bolohan và Neil Mckay vì đã 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 này.