D.JOUNG

OffScreenCanvas + Web Worker 적용 후 Chrome 개발자 도구 성능 탭에서 실행 시간 측정해보기 본문

기타

OffScreenCanvas + Web Worker 적용 후 Chrome 개발자 도구 성능 탭에서 실행 시간 측정해보기

디정 2025. 1. 15. 20:49

개요

 

시작 화면에서 마우스 경로를 따라 움직이는 커스텀 마우스를 그리기 위해 <canvas> 태그를 사용하였다. Canvas API의 OffScreenCanvas 인터페이스는 화면에 노출되지 않는 캔버스를 제공하여 Canvas API의 성능 최적화를 돕는다.

DOM 작업을 다른 스레드에게 분업하여 병렬 처리를 하면 좋겠지만, 기본적으로 멀티 스레드는 HTML 요소 객체를 참조하거나 수정하는 게 불가능하다. DOM Tree와 CSSOM을 생성하고, Render Tree를 만들고, Render Tree 기반으로 화면을 그리는 일련의 과정은 동기적으로 작동하는 부분이 있기 때문에, 여기에 멀티 스레드가 관여했다가는 순서가 이상하게 꼬일 수 있기 때문이다.

 

하지만 HTMLCanvasElement(<canvas> 태그)는 특별히 transferControlToOffscreen() 메소드를 사용하여 다른 스레드의 OffScreenCanvas로 자신의 제어권을 전송할 수 있다. 즉, 제어권을 양도한 다른 스레드 내에서 HTMLCanvasElement 속성에 접근하거나 수정하는 작업이 가능한 것이다.

 

그렇다면, Web Worker와 OffScreenCanvas를 사용하여 메인 스레드의 작업량을 분담하였을 때 성능이 얼마나 개선될 수 있을까?

 

크롬 브라우저 개발자 도구의 성능탭을 통해 세 가지 방식으로 OffScreenCanvas 적용 전후를 비교해보았다.

 

 

성능 측정 환경

  • pnpm build
  • pnpm run start
  • Chrome 시크릿모드 탭
  • 브라우저 화면 사이즈 970 x 1065 px

OffScreenCanvas 적용 전 드로잉 로직

  • 마우스 좌표를 16ms 간격으로 샘플링하여 드로잉 배열에 추가
  • requestAnimationFrame 에서 선을 그리는 drawAni 함수 호출
  • 16ms 간격으로 드로잉 배열의 좌표들을 lineToquadraticCurveTo 메소드로 연결 (곡선 드로잉)
  • stroke() 메소드로 OffScreenCanvas에 드로잉

성능 측정 방법

  • OffScreenCanvas 적용 전 후를 동일한 드로잉 배열로 테스트하기 위해, 16ms 간격으로 실제 마우스 움직임을 트래킹하여 좌표 추출
  • 16ms 간격으로 드로잉 배열에 추출한 좌표를 하나씩 추가.
  • 이후 로직은 기존 드로잉 로직과 동일

 

첫 번째 방법. 메인 스레드 작업 시간 비교

  • CPU 제한 없음, 드로잉 배열 좌표 수 2000개로 고정
  • 최대한 정확하게 측정하기 위해 구간 길이 별(30초, 3초, 10초, 20초)로 체크

1. 30초 길이 구간으로 비교

 

2. 3초 길이 구간으로 비교

 

3. 10초 길이 구간으로 비교

 

4. 20초 길이 구간으로 비교

 

전반적으로 증감율이 동일했고, 적용 전/후의 평균을 계산하면 다음과 같았다.

  • 스크립트 : 적용 전에 비해 약 48% 감소
  • 렌더링 : 적용 전에 비해 약 96% 감소
  • 페인팅 : 적용 전에 비해 약 97% 감소
  • 시스템 : 적용 전에 비해 약 75% 감소
  • 유휴 상태 : 적용 전에 비해 약 4.8% 증가

Web Worker와 OffScreenCanvas를 적용하고 나니 작업 시간이 전반적으로 감소했다. 우선 렌더링과 페인팅 단계 시간이 90% 이상의 감소율로 확연히 줄었다. 캔버스 드로잉 작업을 Worker Tread로 이양함으로써 렌더링과 페인팅 작업을 Blocking하지 않게 됐기 때문일 것으로 추측한다.

 

스크립트 시간의 경우 메인 스레드에서 requestAnimationFrame을 사용하여 직접 드로잉하던 로직을 Worker Thread로 옮겼기 때문에 당연히 감소하게 된다. 사실 Web Worker를 사용한다는 것은 기존 작업량에 Web Worker 객체를 생성하고 좌표 데이터를 Worker Thread로 넘기는 작업이 추가되는 것이기 때문에, 작업자 입장에서는 오히려 할 일이 늘어났다고 볼 수도 있다.

 

그러므로 Web Worker를 사용할 때에는 과연 옮기려는 계산 로직이 Worker Thread로 양도할 정도로 메인 스레드를 방해하고 있는 것이 맞는 지에 대한 판단이 중요할 듯 하다.

 

 

두 번째 방법. 타임라인 비교

worker Thread & OffScreenCanvas 적용 전후 차이는 성능 탭의 타임라인에서도 확인할 수 있었다. 하지만 CPU 제한이 없는 환경에서는 바가 짧아 관찰이 어려웠고, 그 이유로 20배 제한 환경에서 테스트하였다.

  • 아래 결과에서 적용 전 버전은 16ms 안에 메인 스레드 작업을 전부 처리하지 못하지만, 6배 제한 환경에서까지는 모든 작업을 잘 처리했다.

OffScreen & Web Worker 적용 전

  • CPU 성능을 20배 지연시킨 탓에 Frame Rate가 33ms까지 늘어나는 모습을 관찰할 수 있다.
  • 스타일 계산, 사전 페인트, 계층화 항목과 커밋 항목이 있다. (스…산 막대 오른쪽의 두 막대가 각각 사전 페인트와 계층화 막대이다.)
  • 커밋 단계 까지의 작업이 16ms 내에 처리되지 못해, GPU 랜더링 주기가 불규칙하다.

 

 

OffScreen & Web Worker 적용 후

  • CPU 성능을 20배 지연시켰어도 Frame Rate가 16ms 간격으로 규칙적이다.
  • 새 프레임 주기가 시작할 때 Web Worker 와 GPU 작업을 실행하고, 이후 메인 스레드의 작업이 완료되는 타임라인이 지연 없이 규칙적으로 반복된다.

 

 

 

세번째 방법. 캔버스 드로잉 작업 소요시간 측정

위에서 이야기했 듯, Web Worker를 생성하고 그 워커 스레드 내에서 OffScreenCanvas를 사용하는 일은 오히려 작업량이 늘어나는 일이기도 하다. 그렇다면, Worker Thread+OffScreenCanvas를 적용했을 때 작업량은 얼마나 늘어나고, 또 브라우저 성능 상 얼마나 효과가 있을까?

 

performance.now() 는 밀리초 단위의 현재 타임스탬프를 반환한다. 아래에서는 이 performance.now()를 드로잉을 시작하는 시점에서 한번 체크하고, 끝나는 시점에 한번 체크해 종료 시간- 시작 시간을 계산해보았다.

  • CPU 제한 없음, CPU 4배 제한, CPU 6배 제한, CPU 20배 제한 환경에서 측정하였다.

 

CPU 제한 없음

<적용 전>

Start Time : 625.8999999761581

End Time : 16686.399999976158

total : 16060.5

 

<적용 후>

startTime  : 728.6000000238419

End Time  : 16847.899999976158

total : 16119.299999952316

 

CPU X4 제한

<적용 전>

Start Time   : 597.2000000476837

End Time  : 17571.400000095367

소요 시간 : 16974.200000047684

 

<적용 후>

Start Time  : 582.3999999761581

End Time  : 17119.399999976158

소요 시간  : 16537

 

CPU X6 제한

<적용 전>

Start Time  : 861.2000000476837

End Time  : 18544.200000047684

소요 시간  : 17683

 

<적용 후>

Start Time  : 889.1000000238419

End Time  : 17978.100000023842

소요 시간  : 17089

 

CPU 20x 제한 

정확한 측정을 위해 1차, 2차로 2회 모니터링하였다.

<적용 전>

1차

Start Time  : 4458

End Time  : 63231.700000047684

소요 시간 : 58773.700000047684

2차

Start Time  : 5881.799999952316

End Time  : 59635.10000002384

소요 시간 : 53753.300000071526

 

<적용 후>

1차

Start Time  : 4977.200000047684

End Time  : 29352.700000047684

소요 시간 : 24375.5

2차

Start Time  : 5886.700000047684

End Time  : 30566.799999952316

소요 시간 : 24680.099999904633

 

제한없음 환경에서는 적용 후 수치가 100ms 차이 정도로 더 길지만, CPU 제한을 걸 수록 Worker Thread + OffScreenCanvas를 적용한 환경에서의 실행 시간이 더 짧아졌다. X20 지연 환경에서는 거의 2배 정도의 차이가 발생했다.

적용 전 환경에서는 메인 스레드가 16ms 내에 작업을 전부 완료하지 못해, 작업이 다음 프레임으로 밀리고 밀리면서 성능 지연이 발생한 것이다.

 

 

결론

  • Worker Thread를 사용할 때에는 Worker Thread에 양도하려는 로직이 Worker 객체를 생성하고 워커 스레드로 데이터를 복사하는 추가 비용을 지불할 만큼 방해가 되는 게 맞는 지 생각해보자.
  • Worker Thread + OffScreenCanvas 조합은 CPU 연산이 많아 메인 스레드가 16ms 안에 모든 작업을 처리하지 못할 경우, 충분히 적용해볼 만한 최적화 시도다.

 

 

참고 자료

https://developer.chrome.com/docs/devtools?hl=ko

 

Chrome DevTools  |  Chrome for Developers

Chrome DevTools로 웹 애플리케이션을 디버그하고 최적화하세요.

developer.chrome.com