Powrót do strony głównej

Zarządzanie pamięcią ArrayBuffer w JS: triki

Artykuł analizuje techniki przyspieszania zwalniania dużych ArrayBuffer w JS poprzez stan detached z MessageChannel i Worker. Pokazano kod, wyniki testów (-310 MB w Chrome) i kompatybilność. Dla aplikacji high-load typu strumienie wideo.

Triki zwalniania pamięci ArrayBuffer w przeglądarkach
Advertisement 728x90

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.

Google AdInline article slot
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.

Google AdInline article slot

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.

Google AdInline article slot

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

Advertisement 728x90

Czytaj dalej