홈으로 돌아가기

JS에서 ArrayBuffer 메모리 관리: 트릭

이 기사는 MessageChannel과 Worker를 통한 분리 상태로 JS에서 대형 ArrayBuffer 해제를 가속화하는 기술을 분석합니다. 코드, 테스트 결과(Chrome에서 -310 MB), 호환성을 보여줍니다. 비디오 스트림 같은 고부하 애플리케이션에.

브라우저에서의 ArrayBuffer 메모리 해제 트릭
Advertisement 728x90

JavaScript ArrayBuffer 메모리 해제 속도 높이는 법

JavaScript의 가비지 컬렉터는 메모리를 자동으로 관리하지만, 대용량 ArrayBuffer를 초당 몇 MB씩 지속적으로 처리하면 지연이 발생합니다. Chrome 같은 브라우저에서 메모리 사용량이 수백 MB까지 치솟는데, 특히 비디오 스트리밍 시 두드러집니다. Firefox는 상대적으로 잘 처리하지만 여전히 피크가 나타납니다. 32비트 프로세스나 RAM이 제한된 모바일 기기에서 치명적입니다.

ArrayBuffer는 바이너리 데이터 처리를 위해 설계됐지만 수동 해제나 크기 조정 메서드가 없습니다. ArrayBuffer.transfer() 같은 제안은 아직 널리 지원되지 않습니다. 버퍼의 detached 상태(byteLength=0, 접근 시 TypeError 발생)로 전환하면 GC가 즉시 메모리를 회수합니다.

MessageChannel을 활용한 ArrayBuffer 분리 기법

MessageChannel은 컨텍스트 간 데이터 전송에 사용됩니다. port2를 닫으면 port1이 분리되며, 데이터가 이벤트 루프를 기다리지 않고 바로 GC로 넘어갑니다.

Google AdInline article slot
const GarbageBin = (() => {
    /** @type {MessageChannel | null} */
    let messageChannel = null;

    /**
     * ArrayBuffer, TypedArray, DataView 메모리 해제 속도 향상
     * @param {any} junk
     */
    function dispose(junk) {
        if (typeof junk === 'object' && junk) {
            const buffer = junk.buffer || junk;
            if (buffer.byteLength) {
                if (!messageChannel) {
                    messageChannel = new MessageChannel();
                    messageChannel.port2.close();
                }
                messageChannel.port1.postMessage(buffer, [buffer]);
            }
        }
    }

    return { dispose };
})();

사용법:

let buffer = new ArrayBuffer(1_000_000);
// 버퍼 작업...
GarbageBin.dispose(buffer);
buffer = null;

호환성: Chrome 55+, Firefox 50+에서 동작. Chrome 56에서는 데이터가 남아도는 문제 있음. 실제 시나리오에서 피크 메모리 100MB 감소.

제한사항: asm.js 힙(Firefox 49)에는 사용 불가. 버전 간 불안정.

Google AdInline article slot

Worker를 활용한 배치 메모리 해제 기법

Worker.postMessage는 ArrayBuffer를 분리합니다. 워커 종료(self.close()) 시 스레드 메모리 전체가 즉시 해제됩니다.

최적화: 생성/파괴 오버헤드를 줄이기 위해 ~10MB 누적 후 종료.

const GarbageBin = (() => {
    const BIN_CAPACITY = 10_000_000;

    /** @type {Worker | null} */
    let worker = null;
    let workerURL = '';
    let bytesInWorker = 0;

    function dispose(junk) {
        if (typeof junk === 'object' && junk) {
            const buffer = junk.buffer || junk;
            if (buffer.byteLength) {
                if (!worker) {
                    if (!workerURL) {
                        workerURL = URL.createObjectURL(new Blob([
                            `'use strict'; self.onmessage = ({data}) => { if (!data) self.close(); };`
                        ], {type: 'application/javascript'}));
                    }
                    worker = new Worker(workerURL, {name: 'GarbageBin'});
                }
                bytesInWorker += buffer.byteLength;
                worker.postMessage(buffer, [buffer]);
                if (bytesInWorker > BIN_CAPACITY) {
                    clear();
                }
            }
        }
    }

    function clear() {
        if (worker) {
            worker.postMessage(null);
            worker = null;
            bytesInWorker = 0;
        }
    }

    return { dispose, clear };
})();

종료 시 GarbageBin.clear() 호출. 결과: Chrome -310MB, Firefox -70MB 피크 메모리 감소.

Google AdInline article slot

문제점: Firefox 55+에서 가상 주소 공간 누수.

성능 비교 및 권장사항

40분 테스트: CPU 사이클은 오차 범위 내. Firefox 합성 테스트에서 MessageChannel이 Worker보다 느림.

| 기법 | Chrome 효과 | Firefox 효과 | 호환성 |

|---------------|-------------|--------------|-----------------|

| MessageChannel | -100 MB | 안정적 | 낮음 |

| Worker | -310 MB | -70 MB | 높음 (FF 55+ 제외) |

  • 프로덕션에서는 Worker 사용: Chrome에서 더 큰 효과, 안정적.
  • 대상 브라우저에서 테스트: GC 알고리즘 지속 진화.
  • asm.js/WebAssembly 힙에는 피함.
  • 작업 집합 모니터링: perfmon/프로세스 탐색기 활용.

Worker는 고부하(비디오, 스트림)에서 빛납니다.

주요 요약

  • 두 기법 모두 postMessage로 ArrayBuffer 분리, GC 속도 향상.
  • Worker는 10MB 배치로 오버헤드 최소화.
  • 효과: Chrome 스트리밍에서 최대 310MB 피크 감소.
  • CPU 오버헤드 실제 시나리오 <1%.
  • 호환성 테스트: GC 변경으로 버전 간 동작 불안정.

— Editorial Team

Advertisement 728x90

다음 읽기