Dowiedz się, jak mierzyć wykorzystanie pamięci przez stronę internetową w środowisku produkcyjnym, aby wykrywać regresje.
Przeglądarki automatycznie zarządzają pamięcią stron internetowych. Za każdym razem, gdy strona internetowa tworzy obiekt, przeglądarka przydziela „pod maską” fragment pamięci na jego przechowywanie. Pamięć jest ograniczonym zasobem, więc przeglądarka wykonuje operację czyszczenia pamięci, aby wykryć, kiedy obiekt nie jest już potrzebny, i zwolnić odpowiedni fragment pamięci.
Wykrywanie nie jest jednak idealne, a problem stopu Alana Turinga udowodnił, że idealne wykrywanie jest niemożliwe. Dlatego przeglądarki przybliżają pojęcie „obiekt jest potrzebny” do pojęcia „obiekt jest osiągalny”. Jeśli strona internetowa nie może uzyskać dostępu do obiektu za pomocą jego zmiennych i pól innych dostępnych obiektów, przeglądarka może bezpiecznie odzyskać obiekt. Różnica między tymi 2 pojęciami prowadzi do wycieków pamięci, jak pokazano w tym przykładzie.
const object = {a: new Array(1000), b: new Array(2000)};
setInterval(() => console.log(object.a), 1000);
W tym przypadku większa tablica b nie jest już potrzebna, ale przeglądarka jej nie odzyskuje, ponieważ nadal jest dostępna przez object.b w wywołaniu zwrotnym. W ten sposób dochodzi do wycieku pamięci większej tablicy.
Wycieki pamięci są powszechne w internecie, co widać w tym badaniu. Łatwo jest go wprowadzić, zapominając o wyrejestrowaniu detektora zdarzeń, przypadkowo przechwytując obiekty z elementu iframe, nie zamykając skryptu, gromadząc obiekty w tablicach itp. Jeśli na stronie internetowej występują wycieki pamięci, jej wykorzystanie pamięci rośnie z czasem, a strona wydaje się użytkownikom powolna i przeładowana.
Pierwszym krokiem do rozwiązania tego problemu jest jego zmierzenie. Nowy interfejs performance.measureUserAgentSpecificMemory() API umożliwia deweloperom mierzenie wykorzystania pamięci przez strony internetowe w środowisku produkcyjnym, a tym samym wykrywanie wycieków pamięci, które nie zostały wykryte podczas testów lokalnych.
Czym różni się performance.measureUserAgentSpecificMemory() od starszego interfejsu API performance.memory?
Jeśli znasz dotychczasowy niestandardowy interfejs API performance.memory,
możesz się zastanawiać, czym różni się od niego nowy interfejs API. Główna różnica polega na tym, że stary interfejs API zwraca rozmiar sterty JavaScriptu, a nowy interfejs API szacuje pamięć używaną przez stronę internetową. Ta różnica staje się istotna, gdy Chrome udostępnia tę samą stertę wielu stronom internetowym (lub wielu instancjom tej samej strony internetowej). W takich przypadkach wynik starego interfejsu API może być dowolnie nieprawidłowy. Ponieważ stary interfejs API jest zdefiniowany w terminach specyficznych dla implementacji, takich jak „sterta”, jego standaryzacja jest niemożliwa.
Kolejną różnicą jest to, że nowy interfejs API wykonuje pomiar pamięci podczas czyszczenia pamięci. Zmniejsza to szum w wynikach, ale ich wygenerowanie może trochę potrwać. Pamiętaj, że inne przeglądarki mogą zdecydować się na wdrożenie nowego interfejsu API bez polegania na usuwaniu nieużywanych obiektów.
Sugerowane przypadki użycia
Wykorzystanie pamięci przez stronę internetową zależy od czasu wystąpienia zdarzeń, działań użytkownika i zbierania nieużytków. Dlatego interfejs API służący do pomiaru pamięci jest przeznaczony do agregowania danych o wykorzystaniu pamięci w środowisku produkcyjnym. Wyniki poszczególnych połączeń są mniej przydatne. Przykładowe przypadki użycia:
- wykrywanie regresji podczas wdrażania nowej wersji strony internetowej w celu wykrycia nowych wycieków pamięci;
- testowanie A/B nowej funkcji w celu oceny jej wpływu na pamięć i wykrywania wycieków pamięci;
- powiązanie wykorzystania pamięci z czasem trwania sesji w celu sprawdzenia, czy występują wycieki pamięci;
- powiązanie wykorzystania pamięci z danymi o użytkownikach, aby poznać ogólny wpływ wykorzystania pamięci;
Zgodność z przeglądarką
Obecnie interfejs API jest obsługiwany tylko w przeglądarkach opartych na Chromium, począwszy od Chrome 89. Wynik interfejsu API zależy w dużym stopniu od implementacji, ponieważ przeglądarki mają różne sposoby reprezentowania obiektów w pamięci i różne sposoby szacowania wykorzystania pamięci. Przeglądarki mogą wykluczać niektóre regiony pamięci z rozliczania, jeśli prawidłowe rozliczanie jest zbyt kosztowne lub niemożliwe. Dlatego wyników nie można porównywać między przeglądarkami. Porównywanie wyników ma sens tylko w przypadku tej samej przeglądarki.
Jak korzystać z aplikacji performance.measureUserAgentSpecificMemory()
Wykrywanie funkcji
Funkcja performance.measureUserAgentSpecificMemory będzie niedostępna lub może zakończyć się błędem SecurityError, jeśli środowisko wykonawcze nie spełnia wymagań bezpieczeństwa dotyczących zapobiegania wyciekom informacji między domenami.
Opiera się na izolacji od zasobów z innych domen, którą strona internetowa może aktywować przez ustawienie nagłówków COOP+COEP.
Obsługę można wykryć w czasie działania:
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);
}
Testowanie lokalne
Chrome wykonuje pomiar pamięci podczas czyszczenia pamięci, co oznacza, że interfejs API nie zwraca wyniku natychmiast, ale czeka na następne czyszczenie pamięci.
Wywołanie interfejsu API wymusza usunięcie nieużywanych obiektów po upływie określonego czasu, który jest obecnie ustawiony na 20 sekund, ale może nastąpić wcześniej. Uruchomienie Chrome z flagą wiersza poleceń --enable-blink-features='ForceEagerMeasureMemory' skraca czas oczekiwania do zera i jest przydatne podczas lokalnego debugowania i testowania.
Przykład
Zalecane użycie interfejsu API polega na zdefiniowaniu globalnego monitora pamięci, który próbkuje wykorzystanie pamięci przez całą stronę internetową i wysyła wyniki na serwer w celu agregacji i analizy. Najprostszym sposobem jest okresowe próbkowanie, na przykład co M min. Wprowadza to jednak błąd do danych, ponieważ między próbkami mogą wystąpić skoki wykorzystania pamięci.
Poniższy przykład pokazuje, jak przeprowadzić bezstronne pomiary pamięci za pomocą procesu Poissona, który gwarantuje, że próbki będą występować z równym prawdopodobieństwem w dowolnym momencie.
Najpierw zdefiniuj funkcję, która planuje następny pomiar pamięci za pomocą setTimeout() z losowym interwałem.
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);
}
Funkcja measurementInterval() oblicza losowy interwał w milisekundach, tak aby średnio co 5 minut był wykonywany 1 pomiar. Jeśli interesują Cię obliczenia matematyczne związane z tą funkcją, zapoznaj się z artykułem o rozkładzie wykładniczym.
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
Na koniec asynchroniczna funkcja performMeasurement() wywołuje interfejs API, rejestruje wynik i planuje następny pomiar.
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();
}
Na koniec rozpocznij pomiary.
// Start measurements.
scheduleMeasurement();
Wynik może wyglądać tak:
// 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']
},
],
}
Szacunkowe łączne wykorzystanie pamięci jest zwracane w polu bytes. Ta wartość w dużym stopniu zależy od implementacji i nie można jej porównywać w różnych przeglądarkach. Może się nawet zmieniać między różnymi wersjami tej samej przeglądarki. Wartość obejmuje pamięć JavaScriptu i DOM wszystkich elementów iframe, powiązanych okien i instancji roboczych w bieżącym procesie.
Lista breakdown zawiera dodatkowe informacje o używanej pamięci. Każdy wpis opisuje część pamięci i przypisuje ją do zestawu okien, elementów iframe i instancji roboczych zidentyfikowanych przez adres URL. Pole types zawiera listę typów pamięci powiązanych z pamięcią, które są specyficzne dla implementacji.
Ważne jest, aby traktować wszystkie listy w sposób ogólny i nie kodować na stałe założeń opartych na konkretnej przeglądarce. Na przykład niektóre przeglądarki mogą zwracać pusty element breakdown lub attribution. Inne przeglądarki mogą zwracać wiele wpisów w attribution, co oznacza, że nie były w stanie określić, który z tych wpisów jest właścicielem pamięci.
Prześlij opinię
Web Performance Community Group i zespół Chrome chętnie poznają Twoje przemyślenia i doświadczenia związane z performance.measureUserAgentSpecificMemory().
Opisz projekt interfejsu API
Czy coś w interfejsie API nie działa zgodnie z oczekiwaniami? A może brakuje właściwości, które są potrzebne do zrealizowania Twojego pomysłu? Zgłoś problem ze specyfikacją w repozytorium GitHub performance.measureUserAgentSpecificMemory() lub dodaj swoje przemyślenia do istniejącego problemu.
Zgłaszanie problemu z implementacją
Czy udało Ci się znaleźć błąd w implementacji Chrome? A może implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Podaj jak najwięcej szczegółów i proste instrukcje odtworzenia błędu, a w polu Komponenty ustaw Blink>PerformanceAPIs.
Okazywanie wsparcia
Czy planujesz używać performance.measureUserAgentSpecificMemory()? Twoje publiczne wsparcie pomaga zespołowi Chrome ustalać priorytety funkcji i pokazuje innym dostawcom przeglądarek, jak ważne jest ich obsługiwanie. Wyślij tweeta do @ChromiumDev i daj nam znać, gdzie i jak używasz tej funkcji.
Przydatne linki
- Wyjaśniający
- Śledzenie błędów
- Wpis na ChromeStatus.com
- Zmiany od czasu interfejsu API testowania origin
- Zakończone testowanie origin
Podziękowania
Dziękujemy Domenicowi Denicoli, Yoavowi Weissowi i Mathiasowi Bynensowi za sprawdzenie projektu interfejsu API oraz Dominikowi Inführowi, Hannesowi Payerowi, Kentaro Harze i Michaelowi Lippautzowi za inspekcję kodu w Chrome. Dziękuję również Perowi Parkerowi, Philippowi Weisowi, Oldze Belomestnykh, Matthew Bolohanowi i Neilowi Mckayowi za cenne opinie, które pozwoliły znacznie ulepszyć interfejs API.