코드 최적화를 통한 앱 성능 개선

Paul Mo·2024년 3월 17일
0

최근 이직을 하게 되었다. 새로운 회사에서 코드를 살펴보니 최적화가 전혀 되어있지 않았다. 다른 문제들도 많이 보였는데 최적화는 앱이 전체적으로 너무 느리고 버벅였던 원인 중 하나였다. 하지만 이런 상황에서 기회로 삼아 배운 것들을 적용하여 성능을 개선하는 것 또한 재미도 있어 보이고 나의 커리어에 큰 도움이 될거 라고 생각이 들어 해보자고 결정하게 되었다. (사실 코드 퀄리티가 생각보다 심각하긴 했다...)

앱 상태는 생각보다 심각했다. 지속적으로 앱을 사용했을 때 이런 수치가 나왔다.
엄청난 메모리 누수와 CPU를 오버하게 사용하고 있었다. 개인적으로 CPU가 저렇게 사용이 가능한지 몰랐다. 이렇다 보니 기기에서 발열이 생기고 버벅이고 전체적으로 속도가 느렸다.

문제점 파악

부모와 자식 컴포넌트 구조의 불일치

헤더 컴포넌트와 바디 컴포넌트의 데이터가 상관관계가 없음에도 불구하고, 부모 컴포넌트에서 데이터를 패칭하고 자식 컴포넌트로 전달하는 방식으로 구현되어 있어, 데이터 변경 시 불필요한 리렌더링이 발생했다.

Memoization 미사용

useMemo, useCallback, Memo를 적절히 활용하지 않아 값이 비싼 계산이 반복되었다. 그 예시로 FlatList의 renderItem 함수와 같이 UI에 영향을 많이 주는 함수에도 Memoization이 부족했다.

이벤트 리스너 관리 부족

이벤트 리스너를 등록했지만 unMount 시에 제거하지 않아 메모리 누수가 발생했다. 이런 리스너들이 한 두개가 아닌 여러개가 발견 되었었다.

코드의 불필요한 부분

렌더링 함수 내에서 컴포넌트를 생성하는 방식이나, 필요없는 주석 처리된 코드 등의 가독성을 저해하는 불필요한 코드들이 많이 있었다.

개선 방법

부모와 자식 컴포넌트 구조 개선

부모와 자식 컴포넌트를 옳바르게 분리하고 각 컴포넌트에서 필요한 데이터를 해당 컴포넌트에서 패칭하도록 변경하여, 불필요한 데이터 전달과 리렌더링을 줄였다.

Memoization 활용

useMemo, useCallback, Memo를 적절히 사용하여 값이 비싼 계산을 최소화하고, 불필요한 리렌더링을 방지하였다.

이벤트 리스너 관리

모든 이벤트 리스너를 unMount 시에 제거하여 메모리 누수를 막았다.

코드 개선

불필요한 코드를 수정 및 삭제하고, 주석된 코드들도 모두 삭제했다. 그리고 Switch와 if-else문을 활용하여 가독성을 높였다.

  1. 부모와 자식 컴포넌트들이 제대로 나누어져있지 않는다
  • 예시를 들자면 헤더 컴포넌트와 바디 컴포넌트의 데이터들은 상관관계가 없음에도 그들의 부모 컴포넌트에서 각 데이터를 패칭해서 자식 컴포넌트로 전달하다 보니 헤더에서 데이터가 변경되면 바디도 같이 re-rendering 된다.
  1. useMemo와 useCallback 그리고 Memo 자체의 사용성 X
  • 값이 비싼 계산이 들어가는 함수나 변수를 그대로 사용한다. props를 받는 컴포넌트들을 Memo 없이 사용한다. re-rendering이 될 때마다 다시 계산하고 re-rendering 횟수 자체도 많아진다.
  • FlatList의 renderItem 함수 같은 UI에 영향을 많이 주는 Memoization이 중요한 함수에도 useCallback을 사용하지 않아 FlatList에 많은 데이터들이 지속적으로 rendering이 일어난다.
  1. 이벤트 리스너
  • 이벤트 리스너를 등록 했지만 unMount시에 제거하지 않아 중복적으로 리스너가 생성되서 메모리 누수를 일으킴
  1. 이상한 코드들
  • renderer function을 만들어서 Render안에 컴포넌트를 생성하는 함수를 호출한다. React가 해주는 부분을 굳이 수동으로 본인이 직접하는 기분?
  • useMemo나 useCallback을 사용했지만 맞물리는 함수나 변수에는 Memoization을 하지 않아 지속적으로 새로운 함수와 값을 계산해 내는 코드들
  • Switch문이나 if-else문들 사용하면 되는데 Map 객체를 사용해서 key값을 가지고 value에 컴포넌트를 값으로 return 하는 함수
  • 오래 사용하지 않는 주석으로 처리된 코드들
  • 또 뭐가 있을까?

개선 방법
문제점을 파악했으니 그대로 작업 하면 된다.
1. 부모와 자식 컴포넌트들을 제대로 나누고 각 컴포넌트에서 필요한 데이터를 패칭한다.
2. useMemo, useCallback, Memo를 한다.

  • 계산이 복잡하고 자주 rendering을 일으킬 것 같은 값이나 함수 그리고 컴포넌트에 각각 알맞는 방법으로 memoization을 한다.

useMemo
useCallback
Memo
3. 이벤트 리스너 제거

  • 모든 이벤트 리스너들을 확인해서 unMount시에 각 리스너들을 모두 return 함수에 넣어 제거시켜 메모리 누수를 막는다.
  1. 이상한 코드들 수정
  • 컴포넌트를 생성하는 함수에서 컴포넌트를 return 하는 useMemo로 변경
  • useMemo와 useCallback의 dependency list에 포함되는 함수나 값을 memoization 한다.
  • 알아보기 쉽게 (단어가 생각이 안남) Switch와 if-else문으로 한눈에 알아보게 코드 수정
  • 주석 제거

결과

CPU는 100% 위로 여전히 높지만 50%로 줄였고 Memory 85% 정도로 확연하게 줄어들었다. 속도도 전에 비해 굉장히 빨라졌는데 회사 직원분중에 한분이 안드로이드 사용자인데 새로운 앱이 된 것 같다는 말씀과 감사하다는 말씀을 해주셔서 너무 뿌듯했다. 그리고 CPU가 여전히 높은 이유는 다른 이유가 있었다. 이 문제는 다음 글에 문제와 해결 방법을 공유하려고 한다.

마무리

성능 최적화는 앱의 사용성을 향상시키고 사용자 만족도를 높이는 데 중요한 요소이다. 문제를 파악하고 개선하는 과정은 어렵고 복잡하지만, 지금까지의 경험으로 보다 쉽게 문제를 파악하고 개선 할 수 있었다. 이번 기회를 통해 내가 직접 앱을 최적화 하고 그 결과를 눈으로 볼 수 있는 기회가 될 수 있었다. 앞으로도 지속적인 코드 리팩토링과 성능 최적화를 통해 우리의 앱을 더욱 효율적으로 만들어 나가 도록 노력해야겠다.

profile
프론트 엔드 개발자

0개의 댓글

관련 채용 정보