useMemo with performance tab

김동하·2023년 3월 24일
2

react

목록 보기
29/31

들어가며

useCallback, useMemo를 쓰면 매번 이 상황에서 써야 하는 게 맞나 라는 생각이 들었다. 이제는 확실히 언제 써야하는지를 판단해보자!!

예제

검색 앱이 있다고 가정하자. 기능은 세 가지다.

  1. input에 value를 입력하면 전체 데이터를 순회하며 filter된 검색결과를 뱉는다.

  2. 리스트에 있는 아이템을 클릭하면 해당 아이템을 기준으로 filter된 검색결과를 뱉는다.

  3. force renerder 버튼을 누르면 강제 리렌더를 한다.

전체 데이터는 무려 16,000다. value를 입력할 때마다 엄청나게 반응 속도가 느린 것을 확인할 수 있다!

그럼 이 녀석들의 퍼포먼스를 최적화 해보자.

문제

왜 이렇게 느린 것인지 확인해 보기 위해 performance 탭에 들어간다.

상황은 이렇다. 리스트 아이템의 value를 가져온다. 그러면 전체 데이터는 value 기준으로 filter 된다. 그리고 어떤 외부 요인으로 리렌더링을 하게 된다고 가정했을 때 (예제에서는 force redender 버튼으로)

이렇게 무언가 CPU에 변화가 생겼음을 알 수 있다.(저 노란 타임라인은 스크립트 관련 부분인데 뭐라고 하는지 명칭을 정확히 모르겠음)

그리고 그 아래를 쭉쭉 살펴보면 리액트 내부 작업들이 나오고 App이 나오는데 그중 가장 하위에 있는 matchSorter라는 함수를 찾을 수 있다.

matchSorter는 라이브러리인데 value를 받으면 전체 데이터를 조회해서 알맞은 검색 결과를 리턴하는 함수다.

즉, 강제 리렌더링이 되었을 때, value의 변화가 없었음에도 getItem 함수가 호출되어 무려 16,000개의 데이터를 다시 조회했다는 것이다.

자기 자신은 4ms 정도 소요될 일을 했지만, 함수가 호출되며 다른 children에게 130ms가 실행될 영향을 미친 것이다.

위에서 볼 수 있듯 리렌더링이 되면서 APP -> getItems -> matchSorter가 차례대로 호출된다. 이제 해야할 것은 value가 바뀌지 않으면 getItems를 호출하지 않는 것!

이렇게 getItem을 memo 하고 아까와 동일한 플로우(value 선택 후 강제 리렌더링)을 했을 때 퍼포먼스 탭을 확인해보면

스크립트 사용량이 memo 를 하지 않았을 때보다 확연히 줄어든 것을 확인할 수 있다!!

심지어 요약 패널에서는 App을 찾을 수도 없다.

154ms 에서 2ms로 엄청나게 성능이 좋아진 것을 볼 수 있다!!

** 위 결과는 development, x6 slowdown 기준이다

further

나는 진짜 여기서 만족하는데(진짜 만족해서 친구들에게 앞으로 이렇게 퍼포먼스 체크하자고 말했다) 친애하는 kent 선생님은 아직 부족하다고 말씀하셨다. 뭐가 부족한지 알아보자.

16ms과 정크

1 프레임당 소요할 수 있는 시간은 약 16ms가 될 것입니다. 이 숫자가 중요합니다. 이 숫자에 프레임 드롭의 의미가 담겨있습니다. 한 프레임 안에서 작업을 수행하는데 걸리는 시간이 16ms가 넘어가면, smooth한 화면을 보여줄 수 없게 됩니다. 화면이 둑둑 끊이면서 보이게 될 것인데, junk라는 단어로 이것을 명명하는 것 같습니다.

즉, 한 프레임 당 작업의 속도는 16ms 이하가 되어야 화면이 자연스럽게 그려진다는 것이다.

memo를 적용해도 해당 script의 작업 속도가 48ms가 된다. js는 싱글 스레드니까 만약 작업 속도가 16ms보다 크면 브라우저는 업데이트하지 못하고 버벅이게 된다.

이를 개선하기 위해서는 작업 비용이 큰 일을 어딘가에 맡기고 일이 완료되면 다시 브라우저에게 보내야 한다. 그 '어딘가'가 바로 web worker

web worker는 예전에 setInterval 이슈가 있어서(브라우저 탭이 inactive되면 우선순위가 낮아짐) 찾다가 봤었는데 무거운 일을 백그라운드 스레드에서 처리할 수 있게 해주는 기술이다.

그러니까 위 예제에서 getItem() 같은 무거운 연산을 하는 부분을 web worker 에게 보내버리는 것이다!!

workerize라는 웹팩 로더를 사용하면 된다. filter-cities라는 모듈을 (=getItem()) workerize에 넣는다 (구체적으로 어떤 원리인지는 모르겠다)

브라우저와 web worker간 비동기이기에 workerize를 거친 getItem() 도 비동기로 작동하게 된다.

** workerize 관련하여 버그가 있다.

아무튼 그후 동일한 테스트를 해보면

useMemo을 했을 때 48ms였고 web worker가 43ms다. 음... 선생님 뭐가 좋은지 잘 모르겠는데요... (여러 번 해봤는데 큰 차이는 못 느꼈다)

그냥 이런 것도 있다 정도만 알면 될 거 같다. useMemo가 더 편한긴하다...

결론

비용 계산이 클 때만 useCallback, useMemo를 쓰는 게 좋다. 라고만 알고 있었고 그 비용을 어떻게 계산해야하는지 몰랐는데 이렇게 확인하니 어느 정도 감이 잡히긴 한다. 하면 할수록 정말 섬세하게 개발해야 함을 깨닫고... 갈 길은 멀고...

참고

profile
프론트엔드 개발

0개의 댓글