메모제이션

Date
Created
May 26, 2024 10:47 AM
Tags
2장

주장 1: 섣부른 최적하는 독, 필요한 곳에만

꼭 필요한 곳을 신중히 메모제이션 해야 한다는 주장.
메모제이션도 비용이기 때문에 최적화에 대한 비용을 지불 할 때는 신중해야 한다고 주장함.
function sum (a, b){ return a + b; }
위와 같이 간단한 연산은 메모이제이션을 하는게 좋을까 아니면 새로 계산하는게 좋을까?
위 예제는 극단적이긴 하지만 대부분의 가벼운 작업은 메모리에 저장했다가 꺼내오는 것 보다 그때 바로 계산하는게 더 빠를 수 있다.
메모제이션은 값을 비교하고 렌더링 또는 재계산이 필요한지 확인하는 작업, 메모리에 저장했다가 다시 꺼내오는 작업 두가지 비용이 존재한다.
이 비용이 리렌더링 비용보다 저렴하다고 할 수 있을까? 이건 상황에 따라 다르기 때문에 신중하게 접근해야 하며 섣부른 최적화는 경계해야 한다.
따라서 메모이제이션은 항상 어느 정도 트레이드 오프가 있는 기법이라고 보는 것이 옳음.
이전 결과를 캐시로 저장해 미래에 더 나은 성능을 위해 메모리를 차례대로 점유하게 되고, 렌더링도 렌더링 이지만 메모리에 저장하는 것도 마찬가지로 비용이다.
메모이제이션으로 인해 성능 개선이 렌더링보다 낫지 않다면 결국 안하느니 못하다.

주장2: 렌더링 과정의 비용은 비싸다 싹다 해버리자

두 주장의 공통적인 전제는 일부 컴포넌트에서는 메모이제이션을 하는 것이 성능에 도움이 된다.
섣부른 최적화인지 여부와는 관계없이, 컴포넌트가 렌더링이 자주 일어나며 그 렌더링 사이에 비싼 연산이 포함 되어있고 자식 컴포넌트까지 많이 가지고 있다면 memo나 다른 메모이제이션 방법을 사용하는 것이 이점이 있을 때가 분명히 있다.
우리는 2가지 선택권이 주어지는데
  • memo를 컴포넌트 사용에 따라 일부만 적용하기
  • memo를 일단 그냥 다 박기
1번은 앞선 주장과 같이 이상적인 상황. 그치만 어플리케이션의 규모가 커지고 개발자가 많아지고 컴포넌트가 복잡해 지면 이 주장이 잘 지켜질 수 있을까?
그러니 모든 컴포넌트를 memo로 감싸고 생각해보는건 어떨까.
잘못된 memo로 지불해야 하는 비용은 props에 대한 얕은 비교가 발생하면서 지불해야 하는 비용이다.
메모이제이션을 위해서는 CPU와 메모리를 사용해 이전 렌더링 결과물을 저장해 둬야 하고, 리렌더링할 필요가 없다면 이전 결과물을 사용해야 한다.
이는 리액트의 재조정 알고리즘과 동일한데, 이처럼 어차피 리액트는 기본적인 알고리즘 때문에 이전 렌더링을 다음 렌더링과 구별하기 위해 저장해야 한다.
그래서 memo로 지불해야 하는 비용은 props에 대한 얕은 비교 뿐.
하지만 크기가 커진다면 이 비용도 무시할 수 없다.
반면에 memo를 하지 않았을 때 발생할 수 있는 문제는 다음과 같다.
  • 렌더링을 함으로써 발생하는 비용
  • 컴포넌트 내부의 복잡한 로직의 재실행
  • 그리고 위 두가지 모두가 모든 자식 컴포넌트에서 반복해서 일어남
  • 리액트가 구 트리와 신규 트리를 비료
얼핏 보더라도 memo를 하지 않으면 잠재적인 위험 비용이 커보인다.

useMemo & useCallback

이 둘을 사용해 의존성 배열을 비교하고 필요에 따라 값을 다시 계산하는 과정과 이런 처리 없이 값과 함수를 매번 생성하는 비용중 무엇이 더 저렴한지 매번 계산해야 한다.
그러면 무조검 메모제이션을 하는 방법을 먼저 고민해볼 필요가 있다.
리렌더링이 발생할 때 메모이제이션과 같은 별도의 조치가 없다면 모든 객체는 재생성 되고, 결과적으로 참조가 달라지게 됨.
이 달라진 참조값은 어디에서도 사용하지 않는다면 문제가 되지 않지만 useEffect같은 의존성 베열에 사용된다면 변경된 참조로 다른 부분에 영향을 미칠것이다.
useEffect의 무한 렌더링을 생각하면 됨

결론 및 정리

저자는 개인적으로 공부하는 입장이나 최적화를 위한 시간을 많이 쓸 수 있다면 섣부른 메모이제이션은 지양하며 자세히 살펴보며 메모이제이션할 것을 추천하고,
현업에 있고 성능에 깊게 연구할 시간이 없다면 의심스러운 곳에 모두 적용할 것을 추천함.
일반적으로 props의 얕은 비교를 수행하는 것보다 리액트 컴포넌트의 결과물을 다시 계산하고 실제 DOM까지 비교하는 작업이 더 무겁기 때문.
조금이라도 로직이 들어간 컴포넌트는 메모이제이션이 성능 향상에 도움을 줄 가능성이 큼.
useMemo나 usseCallback도 마찬가지. useCallback의 경우 대부분 다른 컴포넌트의 props로 넘어가는 경우가 많음.
이 props로 넘어갔을 때 참조 투명성을 유지하기 위해서는 useCallback을 사용하는 것이 좋음. useMemo도 마찬가지 props로 넘어가거나 활용할 여지가 있다면 사용하는 것이 좋음.