개요
디버깅을 할 때, 어떤 함수에서 CPU 성능이 높게 출력되는지 확인하고 싶을 때가 있습니다.
이럴 때 크롬 인스펙터를 이용해서 간단하게 CPU 성능을 많이 잡아먹는 함수를 찾을 수 있다는 걸 알게 되어서 한번 정리해 보겠습니다.
문제 상황
저는 Google의 MediaPipe.js 라이브러리를 이용해서 얼굴의 특징점을 추출한 다음 해당 좌표에 Lottie 애니메이션을 렌더링 하는 작업을 진행하고 있었는데요,
이때, 점점 Frame Drop이 일어나고 기기 발열 또한 심한 상태였습니다.
이러한 상황에서 크롬 Inspector를 통해 한번 성능을 점검해 보았습니다.
performance monitor로 대략적인 성능 확인해 보기
먼저 간단히 살펴볼 수 있는 도구는 Chrome의 Peformance Monitor입니다.
Performance Monitor는 실시간(Real-time) 모니터링 도구로 아래와 같은 전반적인 Metric을 쉽게 확인해 볼 수 있습니다.
CPU usage | 자바스크립트 실행 및 렌더링 등에 사용되는 CPU 사용량. 높으면 성능 저하 가능성. |
JS heap size | 자바스크립트가 사용하는 메모리 공간 크기. 누수 여부를 파악할 수 있어요. |
DOM Nodes | 현재 페이지에서 생성된 DOM 요소의 개수. 너무 많으면 렌더링 성능 저하 유발 가능. |
JS event listeners | 등록된 이벤트 리스너 수. 이벤트 누수, 과도한 리스너 등록 여부 확인 가능. |
Documents | 현재 브라우저 프로세스 내에서 로드된 HTML 문서 수. 보통은 1개, iframe/탭/팝업이 많으면 증가함. |
Document Frames | 문서 내에서 사용 중인 프레임(iframe 포함) 수. nested iframe이 많으면 늘어날 수 있음. |
Layouts / sec | 초당 발생하는 레이아웃 계산 횟수. 높으면 렌더링에 부하가 있을 수 있음. |
Style recalcs / sec | 초당 발생하는 스타일 재계산 횟수. CSS 변경에 따라 자주 발생하면 성능 저하 유발 가능. |
하지만 대략적인 수치만을 알 수 있고, 어떤 부분에서 문제가 발생하는지 정확하게는 파악하기 어렵다는 단점이 있습니다.
일단 제 경우에는 거의 CPU가 100%를 찍고 있기 때문에 어떤 부분에서 리소스를 많이 파악하는지 알아볼 필요가 있었습니다.
performance tab 으로 리소스를 많이 사용하는 지점 파악하기
퍼포먼스 탭은 performance monitor와 다르게 웹페이지를 녹화하고 분석하는 방식으로 Record 버튼을 눌러 브라우저의 작업을 타임라인 상에 정밀하게 기록하고 분석합니다. (대충 3~4초 정도 녹화하면 충분)
퍼포먼스 탭은 아래와 같은 trace 데이터 및 metric을 확인할 수 있습니다.
Main Thread Activity | 페이지의 메인 스레드에서 실행된 작업들을 시간 순서대로 보여줍니다. JavaScript 실행, 스타일 계산, 레이아웃, 페인트 등의 작업이 포함됩니다. |
Flame Chart | 함수 호출 스택을 시각적으로 표현하여, 어떤 함수가 얼마나 오랫동안 실행되었는지를 확인할 수 있습니다. |
FPS (Frames Per Second) | 페이지의 렌더링 프레임 속도를 나타냅니다. 낮은 FPS는 애니메이션이나 스크롤 시 버벅임을 유발할 수 있습니다. |
Network Requests | 페이지 로딩 중 발생한 네트워크 요청들을 시간 순으로 표시하여, 각 요청의 시작 시점과 소요 시간을 확인할 수 있습니다. |
Screenshots | 녹화 중 페이지의 스크린샷을 캡처하여, 특정 시점에 페이지가 어떻게 보였는지를 시각적으로 확인할 수 있습니다. |
Timings | 주요 타이밍 이벤트(DOM Content Loaded, First Paint, First Contentful Paint 등)를 표시하여, 페이지 로딩 성능을 분석할 수 있습니다. |
Interactions | 사용자 상호작용(클릭, 스크롤 등)과 그에 따른 응답 시간을 추적하여, 인터랙션 성능을 평가할 수 있습니다. |
Call Tree / Bottom-Up / Event Log | 함수 호출 트리, 시간별 함수 실행 정보, 이벤트 로그 등을 통해 성능 병목 지점을 상세히 분석할 수 있습니다. |
저는 퍼포먼스탭을 통해서 녹화를 진행하고 Call Tree를 통해 병목지점을 한번 파악해 보았는데요, Bottom-up 이나 Call Tree를 이용하면 함수당 걸리는 시간에 대해서 좀 더 자세히 살펴볼 수 있습니다.
둘 다 함수 실행 시간을 기반으로 하지만, 관점 약간 다른데요, Call Tree는 함수들이 어떻게 호출됐는지 흐름을 따라가며 보여주고, Bottom-Up은 그냥 시간 많이 쓴 함수부터 딱 보여줍니다.
그래서 보통은 Bottom-Up으로 가장 무거운 함수를 먼저 찾고, Call Tree로 들어가서 왜 그런지 흐름을 파악하는 것이 좋습니다. 즉 "누가 무거운가?"는 Bottom-Up, "왜 무거운가?"는 Call Tree가 답해줍니다.
Call Tree와 Bottom-Up 을 들어가게 되면 나오는 Total Time, Selft Time의 의미는 아래와 같습니다.
Total Time | 이 함수와 그 하위 함수들이 실행된 총 시간 |
Self Time | 이 함수 자체에서만 소비된 시간 (하위 함수 호출 시간 제외) |
즉 Total Time이 높고 Self Time이 낮을 경우에는 해당 함수가 호출하는 다른 함수에서 작업시간이 오래 걸린다는 뜻이고, Self Time이 높은 경우 해당 함수에서 현재 리소스를 많이 잡아먹고 있다는 뜻입니다.
원인 분석 및 해결
제 경우에는 로티 애니메이션 라이브러리에서 CPU 작업이 많이 일어나서 병목현상이 발생한다는 점을 파악할 수 있었는데요, 여담으로, 제가 사용하고 있는 로티 애니메이션에서 CPU 대신 GPU를 사용할 수 있도록 수정했더니 문제가 해결되었습니다.
lottie-web
After Effects plugin for exporting animations to SVG + JavaScript or canvas + JavaScript. Latest version: 5.12.2, last published: 2 years ago. Start using lottie-web in your project by running `npm i lottie-web`. There are 908 other projects in the npm reg
www.npmjs.com
import lottie from 'lottie-web'
import flowerLottie from '/static/media/lottie/garden/lottie.garden.photo.starbust.json'
/** 통일 염원의 동산 사진 배경 StarDust 효과 **/
export default function StarBustLottie() {
const canvasRef = useRef(null)
const animationRef = useRef(null) // 애니메이션 참조를 위한 ref 추가
const initCanvas = () => {
const canvas = canvasRef.current
if (!canvas) return // canvas가 없을 경우 early return
canvas.width = window.innerWidth
canvas.height = window.innerHeight
const canvasContext = canvas.getContext('2d')
const animationInfo = {
renderer: 'canvas',
loop: true,
autoplay: true,
animationData: flowerLottie,
rendererSettings: {
context: canvasContext,
progressiveLoad: false,
hideOnTransparent: true,
className: 'heart-icon',
scaleMode: 'noScale',
preserveAspectRatio: 'xMidYMid slice',
},
}
// 이전 애니메이션이 있으면 정리
if (animationRef.current) {
animationRef.current.destroy()
}
// 새 애니메이션 로드 및 재생
animationRef.current = lottie.loadAnimation(animationInfo)
animationRef.current.play()
}
useEffect(() => {
initCanvas()
return () => {
// 컴포넌트 언마운트 시 또는 상태 변경 시 애니메이션 정리
if (animationRef.current) {
animationRef.current.destroy()
}
}
}, [])
return (
<canvas
renderer='canvas' // 해당 라인 추가
ref={canvasRef}
className='canvas'
id='lottieStarbustCanvas'
style={{
position: 'absolute',
width: '100%',
height: '100%',
zIndex: 5,
top: 0,
left: 0,
}}
></canvas>
)
}
마무리
오늘은 크롬 인스펙터를 통해 성능을 점검하고, 병목지점을 찾아내서 실제 개선을 했던 경험을 공유해 보았습니다.
알려드린 내용 말고도 크롬 인스펙터를 자주 활용하고 친숙해지면, 찾기 힘든 성능 이슈나 여러 케이스들의 문제 원인을 정확히 파악할 수 있는 시야가 생기기 때문에 미리 스터디를 해놓는 것도 좋을 것 같다고 생각했습니다.
다음번에도 유용한 내용으로 포스팅해보도록 하겠습니다.
감사합니다.
참고
* https://lazy-dev.netlify.app/javascript/how-to-profile-js-performance-in-chrome-browser/
'웹 프론트 > 웹개발 용어 및 개념 정리' 카테고리의 다른 글
JS : blob에 대해 알아보고 data URL을 통해 표현하기 (createObjectURL, removeObjectURL) (5) | 2022.04.08 |
---|---|
HTTP: Content-Type 에 대해 알아보자 (application/json, application/x-www-form-urlencoded, multipart/form-data) (10) | 2022.02.16 |
개발 용어 : 캐리지 리턴(CR), 라인 피드 (LF) 알아보기 (6) | 2021.03.23 |
HTTP 와 TCP의 Keep-Alive (0) | 2018.09.06 |
Key-Frame 애니메이션이란? (0) | 2018.08.09 |