Przyspieszanie zwalniania pamięci ArrayBuffer w JavaScript
Śmieciarka w JavaScript automatycznie zarządza pamięcią, ale przy ciągłym przetwarzaniu dużych ArrayBuffer (kilka megabajtów na sekundę) pojawiają się opóźnienia. W przeglądarkach jak Chrome zużycie pamięci rośnie do setek megabajtów, szczególnie przy strumieniowym przetwarzaniu wideo. Firefox radzi sobie lepiej, ale i tam zdarzają się skoki. To kluczowe dla procesów 32-bitowych i urządzeń mobilnych z ograniczoną RAM.
ArrayBuffer służy do danych binarnych, ale nie ma metod ręcznego zwalniania czy zmiany rozmiaru. Propozycje jak ArrayBuffer.transfer() nie są zaimplementowane w większości przeglądarek. Stan detached bufora (byteLength=0, dostęp powoduje TypeError) pozwala GC szybko pozbyć się pustych buforów.
Trikiem z MessageChannel dla detached ArrayBuffer
MessageChannel przekazuje dane między kontekstami. Zamknięcie port2 sprawia, że port1 staje się detached: dane trafiają do GC bez czekania na cykl.
const Śmietnik = (() => {
/** @type {MessageChannel | null} */
let kanałWiadomości = null;
/**
* Przyspiesza zwalnianie pamięci pod ArrayBuffer, TypedArray, DataView.
* @param {any} śmieć
*/
function wyrzuć(śmieć) {
if (typeof śmieć === 'object' && śmieć) {
const bufor = śmieć.buffer || śmieć;
if (bufor.byteLength) {
if (!kanałWiadomości) {
kanałWiadomości = new MessageChannel();
kanałWiadomości.port2.close();
}
kanałWiadomości.port1.postMessage(bufor, [bufor]);
}
}
}
return {wyrzuć};
})();
Użycie:
let bufor = new ArrayBuffer(1_000_000);
// Praca z buforem...
Śmietnik.wyrzuć(bufor);
bufor = null;
Kompatybilność: Działa w Chrome 55+, Firefox 50+. W Chrome 56 dane mogą się zacinać. Efekt: redukcja szczytu pamięci o 100 MB w realnym scenariuszu.
Ograniczenia: Nie dla kupy asm.js (Firefox 49). Niestabilność między wersjami.
Trikiem z Workerem dla wsadowego zwalniania
Worker.postMessage przenosi ArrayBuffer do stanu detached. Zakończenie workera (self.close()) natychmiast zwalnia całą pamięć wątku.
Optymalizacja: gromadzimy ~10 MB przed zamknięciem, minimalizując narzut na tworzenie/zniszczenie.
const Śmietnik = (() => {
const POJEMNOŚĆ_ŚMIETNIKA = 10_000_000;
/** @type {Worker | null} */
let worker = null;
let adresWorkera = '';
let bajtyWWorkerze = 0;
function wyrzuć(śmieć) {
if (typeof śmieć === 'object' && śmieć) {
const bufor = śmieć.buffer || śmieć;
if (bufor.byteLength) {
if (!worker) {
if (!adresWorkera) {
adresWorkera = URL.createObjectURL(new Blob([
`'use strict'; self.onmessage = ({data}) => { if (!data) self.close(); };`
], {type: 'application/javascript'}));
}
worker = new Worker(adresWorkera, {name: 'Śmietnik'});
}
bajtyWWorkerze += bufor.byteLength;
worker.postMessage(bufor, [bufor]);
if (bajtyWWorkerze > POJEMNOŚĆ_ŚMIETNIKA) {
wyczyść();
}
}
}
}
function wyczyść() {
if (worker) {
worker.postMessage(null);
worker = null;
bajtyWWorkerze = 0;
}
}
return {wyrzuć, wyczyść};
})();
Wywołać Śmietnik.wyczyść() przy kończeniu pracy. Efekt: Chrome -310 MB, Firefox -70 MB szczytu pamięci.
Problemy: Wyciek przestrzeni adresowej wirtualnej w Firefox 55+.
Porównanie wydajności i rekomendacje
Testy na 40 minut: cykle CPU w granicach błędu. MessageChannel wolniejszy od Workera w Firefox (syntetyka).
| Trikiem | Efekt w Chrome | Efekt w Firefox | Kompatybilność |
|---------|----------------|-----------------|----------------|
| MessageChannel | -100 MB | Stabilnie | Niska |
| Worker | -310 MB | -70 MB | Wysoka (oprócz Firefox 55+) |
- Używaj Workera w produkcji: stabilniejszy, większy efekt w Chrome.
- Testuj w docelowych przeglądarkach: algorytmy GC się zmieniają.
- Unikaj dla kupy asm.js/WebAssembly.
- Monitoruj perfmon/explorer procesów dla working set.
Worker lepszy przy wysokim obciążeniu (wideo, streamy).
Co ważne
- Oba triki przenoszą ArrayBuffer do detached via postMessage, przyspieszając GC.
- Worker gromadzi 10 MB do batchingu, minimalizując narzut.
- Efekt: do 310 MB redukcji szczytu w Chrome przy streamingu.
- Narzut CPU <1% w realnych scenariuszach.
- Testuj kompatybilność: zmiany GC psują triki między wersjami.
— Editorial Team
Brak komentarzy.