Enhancing Web Worker Performance with Transferable Objects in Angular

2 분 소요

소개

웹 워커(worker)는 복잡한 계산과 다양한 작업을 동시에 처리하는 성능을 향상 시키기 위한 강력한 도구 중 하나로, 별도의 스레드에서 스크립트를 실행하여 메인 스레드의 부하를 줄일 수 있습니다.
그러나 메인 스레드와 워커 간 데이터 전송은 여전히 성능 문제를 야기할 수 있습니다. 이때 Transferable Objects가 강력한 도움을 줄 수 있습니다.

Transferable Objects란?

Transferable Objects는 메인 스레드와 워커 사이에서 데이터를 효율적으로 전송하기 위한 방법 입니다. 이를 통해 데이터를 복사하는 대신 소유권을 전송함으로써 성능을 향상시킬 수 있습니다.

주로 사용되는 Transferable Objects는 다음과 같습니다.

  • ArrayBuffer: 바이너리 데이터를 효율적으로 다룰 수 있는 객체로, 메모리를 공유하는 데에 적합합니다.

  • MessagePort: 워커 간 메시지를 전송하는 데 사용되며, 데이터를 복사하지 않고도 전송할 수 있습니다.

  • ImageBitmap: 이미지 데이터를 처리할 때 유용하게 활용됩니다.

Transferable Objects의 활용 예시

component

먼저, Angular 애플리케이션에서 Transferable Objects를 사용하는 예시 코드를 살펴봅시다.
AppComponent는 웹 워커를 통해 병렬 처리를 수행하고, Transferable Objects를 사용하여 데이터를 주고받습니다.

export class AppComponent implements OnInit, OnDestroy {
  worker = new WorkerController();
  readonly worker$ = new Subject<void>(); // subscription

  ngOnInit(): void {
    this.worker.worker$.pipe(
      tap((response: any) => {
        console.log({response});
      }),
      takeUntil(this.worker$) // subscription
    ).subscribe();
  }

  calculate() {
    this.worker.calculate();
  }

  ngOnDestroy() {
    this.worker$.next(); // unsubscription
    this.worker$.complete();
  }
}

worker instance

instance는 웹 워커를 생성하고 데이터를 주고받는 역할을 합니다. 데이터를 ArrayBuffer 형태로 encoding하여 전송하고, 수신한 데이터를 json으로 decoding하여 Component에 전달합니다.

import { Subject } from 'rxjs';

export class WorkerController {
  worker: any;
  worker$ = new Subject();

  constructor() {
    if (typeof Worker !== 'undefined') {
      // creating web worker
      this.worker = new Worker(new URL('./app.worker', import.meta.url));

      // receive
      this.worker.onmessage = ({ data }: any) => {
        // transferable object 형태
        const resultBuffer = new Uint8Array(data);
        const resultString = new TextDecoder().decode(resultBuffer);
        this.worker$.next(resultString ? JSON.parse(resultString) : '');
      };




    } else {
      console.error('this environment does not support web worker');
    }
  }
  calculate(min: number = 1, max: number = 100) {
    // send
    this.postMessage({
      {
        min,
        max,
      }
    });
  }

  private postMessage(data: any) {
    // json -> string -> buffer 형태로 변환
    const jsonBuffer = this.convertToBuffer(data);
    this.worker.postMessage(jsonBuffer, [jsonBuffer]);
  }
  private convertToBuffer(jsonData: any) {
    return new TextEncoder().encode(JSON.stringify(jsonData)).buffer;
  }
  destroy() {
    this.worker.terminate();
  }
}

worker

worker 에서는 수신한 ArrayBuffer 데이터를 다시 json으로 decoding하여 처리한 뒤 다시 encoding하여 메인 스레드에 전달합니다.

/// <reference lib="webworker" />

addEventListener('message', ({data}) => {
  console.log('getmessage', data);

  // data를 받아 decode 하고 처리한 뒤 다시 encode
  // buffer -> string -> json으로 변환 후 postMessage
    const obj = decodeBuffer(data);

    let sum = 0;
    for(let i=obj.min; i<=obj.max;i++) {
      sum+=(Math.pow(i, 2));
    }

    postMessage(encodeString(sum));
  
});


function decodeBuffer(data: any) {
  const jsonBuffer = new Uint8Array(data);
  const jsonString = new TextDecoder().decode(jsonBuffer);
  return JSON.parse(jsonString);
}

function encodeString(data: any) {
  const resultString = JSON.stringify(data);
  return new TextEncoder().encode(resultString);
}

결론

Transferable Objects를 통해 메인 스레드와 워커 간의 데이터 전송 성능을 향상시킬 수 있습니다. 특히 대용량의 데이터를 다루거나 병렬 처리가 필요한 웹 애플리케이션에서는 이를 적극적으로 활용하여 성능 최적화를 달성할 수 있습니다. Transferable Objects의 활용은 데이터 복사 비용을 최소화하고 메모리를 효율적으로 활용하는 데에 큰 도움이 됩니다.

댓글남기기