Dowiedz się, jak mierzyć wykorzystanie pamięci przez stronę internetową w produkcji, aby wykrywać regresje.
Przeglądarki zarządzają pamięcią stron internetowych automatycznie. Gdy strona internetowa tworzy obiekt, przeglądarka przydziela mu część pamięci „pod maską”. Ponieważ pamięć jest ograniczonym zasobem, przeglądarka wykonuje zbiór elementów, aby wykryć, kiedy obiekt nie jest już potrzebny, i zwolnić blok pamięci.
Wykrywanie nie jest jednak idealne, a udowadniamy, że wykrywanie w 100% jest niemożliwe. Dlatego przeglądarki zastępują pojęcie „wymagany obiekt” pojęciem „dostępny obiekt”. Jeśli strona internetowa nie może dotrzeć do obiektu za pomocą swoich zmiennych i pól innych dostępnych obiektów, przeglądarka może bezpiecznie odzyskać obiekt. Różnica między tymi dwoma pojęciami prowadzi do wycieków pamięci, jak pokazuje poniższy przykład.
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 zwalnia, ponieważ jest nadal dostępna za pomocą object.b
w funkcji wywołania zwrotnego. W ten sposób dochodzi do wycieku pamięci z większego tablicę.
Wycieki pamięci są częste w internecie. Łatwo można wprowadzić błąd, zapominając zarejestrować obiekt nasłuchujący zdarzeń, przypadkowo przechwytując obiekty z ramki iframe, nie zamykając workera, gromadząc obiekty w tablicach itd. Jeśli strona internetowa ma wycieki pamięci, z czasem rośnie jej wykorzystanie pamięci, a użytkownicy mogą odczuwać jej spowolnienie i zawieszanie się.
Pierwszym krokiem do rozwiązania tego problemu jest jego pomiar. Nowy interfejs API performance.measureUserAgentSpecificMemory()
umożliwia programistom pomiar wykorzystania pamięci przez ich strony internetowe w produkcji, a tym samym wykrywanie wycieków pamięci, które nie zostały wykryte podczas testów lokalnych.
Czym interfejs performance.measureUserAgentSpecificMemory()
różni się od starszego interfejsu performance.memory
?
Jeśli znasz i korzystasz z dotychczasowego niestandardowego interfejsu API performance.memory
, możesz się zastanawiać, czym różni się on od nowego interfejsu API. Główna różnica polega na tym, że stary interfejs API zwraca rozmiar stosu JavaScript, a nowy interfejs API szacuje pamięć używaną przez stronę internetową. Ta różnica staje się ważna, gdy Chrome udostępnia ten sam stos wielu stronom internetowym (lub wielu instancjom tej samej strony). W takich przypadkach wynik starego interfejsu API może być dowolnie nieprawidłowy. Ponieważ stary interfejs API jest definiowany w terminach specyficznych dla implementacji, takich jak „heap”, jego standaryzacja jest niemożliwa.
Kolejną różnicą jest to, że nowe API wykonuje pomiar pamięci podczas czyszczenia pamięci podręcznej. Pozwala to ograniczyć szum w wynikach, ale ich wygenerowanie może zająć trochę czasu. Pamiętaj, że inne przeglądarki mogą zdecydować się na wdrożenie nowego interfejsu API bez polegania na zbieraniu elementów do usunięcia.
Sugerowane zastosowania
Korzystanie z pamięci przez stronę internetową zależy od czasu trwania zdarzeń, działań użytkownika i zbierania elementów zbędących. Dlatego interfejs API do pomiaru pamięci jest przeznaczony do agregowania danych o wykorzystaniu pamięci z wersji produkcyjnej. Wyniki poszczególnych połączeń są mniej przydatne. Przykładowe przypadki użycia:
- wykrywanie regresji podczas wdrażania nowej wersji strony internetowej w celu wykrywania nowych wycieków pamięci;
- testy A/B nowej funkcji w celu oceny jej wpływu na wykorzystanie pamięci i wykrywania wycieków pamięci;
- powiązać wykorzystanie pamięci z czasem trwania sesji, aby sprawdzić, czy nie dochodzi do wycieków 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 wersji 89 przeglądarki Chrome. Wynik działania interfejsu API zależy w dużej mierze od implementacji, ponieważ przeglądarki mają różne sposoby przedstawiania obiektów w pamięci i szacowania wykorzystania pamięci. Przeglądarki mogą wykluczać niektóre regiony pamięci z rozliczania, jeśli jest ono zbyt drogie lub niemożliwe. Dlatego wyników nie można porównywać w różnych przeglądarkach. Porównywanie wyników w przypadku tej samej przeglądarki ma sens.
Jak korzystać z aplikacji performance.measureUserAgentSpecificMemory()
Wykrywanie cech
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 zapobiegających wyciekom informacji między domenami.
Funkcja ta opiera się na izolacji zasobów z innych domen, którą strona internetowa może aktywować, ustawiając nagłówki COOP+COEP.
Obsługa może być wykrywana 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 usuwania elementów z pamięci podręcznej, co oznacza, że interfejs API nie rozwiązuje natychmiast obietnicy wyniku, tylko czeka na następne usunięcie elementów z pamięci podręcznej.
Wywołanie interfejsu API powoduje wymuszenie usuwania elementów zbędących po pewnym czasie, który jest obecnie ustawiony na 20 sekund, ale może nastąpić wcześniej. Uruchamianie Chrome z flagą wiersza poleceń --enable-blink-features='ForceEagerMeasureMemory'
powoduje skrócenie limitu czasu do 0 s i jest przydatne do debugowania i testowania lokalnego.
Przykład
Zalecane jest zdefiniowanie globalnego monitora pamięci, który pobiera próbki wykorzystania pamięci przez całą stronę internetową i wysyła wyniki do serwera w celu ich agregacji i analizy. Najprostszym sposobem jest okresowe próbkowanie, na przykład co M
minut. Jednak powoduje to zafałszowanie danych, ponieważ między próbkami mogą występować szczyty pamięci.
Z tego przykładu dowiesz się, jak przeprowadzać obiektywne pomiary pamięci za pomocą procesu Poissona, który gwarantuje, że próbki mają równe prawdopodobieństwo wystąpienia w dowolnym momencie (demonstracja, źródło).
Najpierw zdefiniuj funkcję, która planuje następne pomiary pamięci za pomocą funkcji 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 występowało jedno pomiar. Jeśli interesuje Cię matematyka stojąca za tą funkcją, zapoznaj się z artykułem Rozkład wykładniczy.
function measurementInterval() {
const MEAN_INTERVAL_IN_MS = 5 * 60 * 1000;
return -Math.log(Math.random()) * MEAN_INTERVAL_IN_MS;
}
Na koniec funkcja asynchroniczna 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();
}
W końcu zacznij mierzyć.
// 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']
},
],
}
Szacowany łączny rozmiar pamięci jest zwracany w polu bytes
. Ta wartość jest w dużej mierze zależna od implementacji i nie można jej porównywać w różnych przeglądarkach. Może się ona nawet zmieniać w różnych wersjach tej samej przeglądarki. Wartość obejmuje pamięć JavaScript i DOM wszystkich ramek iframe, powiązanych okien i instancji roboczych w bieżącym procesie.
Lista breakdown
zawiera więcej informacji o wykorzystanej pamięci. Każdy wpis opisuje pewną część pamięci i przypisuje ją do zestawu okien, ramek iframe i instancji roboczych zidentyfikowanych za pomocą adresu URL. Pole types
zawiera listę typów pamięci powiązanych z pamięcią w ramach implementacji.
Ważne jest, aby traktować wszystkie listy w ogólny sposób i nie stosować zaszytych na stałe założeń dotyczących konkretnej przeglądarki. Na przykład niektóre przeglądarki mogą zwracać puste wartości breakdown
lub attribution
. Inne przeglądarki mogą zwracać wiele wpisów w attribution
, co oznacza, że nie mogą odróżnić, który z nich jest właścicielem pamięci.
Prześlij opinię
Grupa ds. wydajności stron internetowych i zespół Chrome chętnie poznają Twoje opinie i wrażenia dotyczące performance.measureUserAgentSpecificMemory()
.
Poinformuj nas o projektowaniu interfejsu API
Czy interfejs API nie działa zgodnie z oczekiwaniami? A może brakuje właściwości, których potrzebujesz do wdrożenia swojego pomysłu? Zgłoś problem ze specyfikacją w repozytorium performance.measureUserAgentSpecificMemory() na GitHubie lub dodaj swoje uwagi do istniejącego problemu.
Zgłaszanie problemów z implementacją
Czy znalazłeś/znalazłaś błąd w implementacji Chrome? Czy implementacja różni się od specyfikacji? Zgłoś błąd na stronie new.crbug.com. Pamiętaj, aby podać jak najwięcej szczegółów, dołączyć proste instrukcje odtwarzania błędu i ustawić Components na Blink>PerformanceAPIs
.
Glitch to świetne narzędzie do szybkiego i łatwego udostępniania informacji o powtarzających się problemach.
Pokaż pomoc
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 wsparcie. Wyślij tweeta do @ChromiumDev i podaj, gdzie i jak z niego korzystasz.
Przydatne linki
- Wyjaśnienie
- Demo | Źródło wersji demonstracyjnej
- Śledzenie błędu
- Wpis na stronie ChromeStatus.com
- Zmiany od czasu testowania origin interfejsu Trial API
- Zakończono testowanie wersji próbnej Origin
Podziękowania
Dziękujemy Domenicowi Denicola, Yoavowi Weissowi i Mathiasowi Bynensowi za sprawdzenie projektu interfejsu API oraz Dominikowi Inführowi, Hannesowi Payerowi, Kentaro Hara i Michaelowi Lippautzowi za sprawdzenie kodu w Chrome. Dziękuję też Perowi Parkerowi, Philippowi Weisowi, Oldze Belomestnykh, Matthew Bolohanowi i Neilowi McKayowi za cenne opinie użytkowników, które znacznie poprawiły interfejs API.
Baner powitalny autorstwa Harrison Broadbent na Unsplash