home

immutable은 왜

글 분류
main
키워드
programming
생성일
2023/03/28 00:04
최근 수정일
2025/02/20 06:39
작성중
글 작성 시 퍼포먼스 테스트를 위해 사용한 Node 버전 → v18.15.0 해당 Node.js의 v8 엔진 버전 → 10.2.154.26

⚽️ 목표

immutable한 값 변화가 좋다는데… performance 차이는 어떨까?
리액트는 immutable 한 값 변화를 활용해 퍼포먼스를 최적화했다는데… 대체 어떤 면에서 그렇다는건데?로 시작한 생각들이다.
“무적권 빠른거 쓰세요”를 말하는 글이 절대 아니다. 화내기 전에 글읽는 5분만 참아달라. 제발..

TL;DR

immutable한 값 변화는 mutable한 값 변화 보다 느리다
하지만 걱정할 필요없다구!
비교해야할 대상이 복잡할 수록(nested object, long array) shallow comparison이 deep comparison보다 빠르다
복잡한 구조의 객체, 배열을 일수록 shallow comparison이 더 빠르다.

1. Immutability?

객체나 값이 생성된 후 변경될 수 없는 특성을 가리키는 프로그래밍 개념
const arr1 = new Array(1000000).fill(0); const arr2 = new Array(1000000).fill(0); // mutable 값 변경 코드 arr1[0] = 1; // immutable 값 변경 코드 const newArr = [...arr2]; newArr[0] = 1;
TypeScript
복사
Immutability의 자세한 설명은 나보다 훨씬 잘 설명하는 선생님들이 계시니.. 자세한 설명은 링크로 대체한다.
muatable한 변화 → 원본 객체 또는 값이 변경될 경우 해당 값을 참조하는 다른 코드에서 side effect가 생길 가능성이 존재한다. → (아! 식상해… 다른 사람들 다 알고있는 사실이잖아?)
해당 객체를 참조하는 다른 함수나 코드에서 예기치 않은 결과 초래한다.
예기치 않은 결과 모든 레퍼런스에서 나오는 제일 중요한 키워드
immutable한 값 변화를 사용함에 대한 개발적(DX) 장점은 확실하다 → 퍼포먼스적으로 차이점은 무엇일까?를 알아보자.

2. performance 비교 테스트 하기 전에

퍼포먼스 비교는 총 두개로 나뉘어진다.
1.
immutable한 값 변화의 퍼포먼스
2.
변경된 값 확인 여부를 위한 shallow comaprision 과 deep comparision 비교
간단한 비교를위해 평균 속도 측정시 소수점 두자리 아래는 모두 버린다.
명확한 테스트 결과를 확인을 위해 극단적인 양의 비교를 수행했다. 알고있다, 실제로 이렇게 많은 비교는 아주 극히 일부 케이스라는걸..

비교 1. immutable한 값 변화와 direct 값 변화 퍼포먼스 비교

테스트 코드

결과

mutable한 값 변화가 훨씬 더 빠름
1회
2회
3회
평균
 immutable 한 값 변화
23.77ms
27.63ms
27.99ms
26.46ms
 mutable 한 값 변화
8.87ms
9.92ms
9.51ms
9.43ms
immutable 하게 바꾸는게 더 많은 메모리 사용과 느린 실행 속도를 유발한다.→ 대부분의 use case에서
값 변경 시, 직접 해당 값을
1. 객체(배열) 복사
2. 값 증가 후 새로운 객체(배열 생성)
라는 프로세스가 존재하기 때문에 직접 해당 주소의 값을 바꾸는게 훨씬 더 빠르고 메모리 소모가 적다.

비교 2. Deep comparison, Shallow Comparison의 퍼포먼스 비교

테스트 코드

결과

비교하는 대상이 복잡해지면 복잡해질 수록 Shallow Comparison이 훨씬 더 빠름
1회
2회
3회
평균
 Shallow Comparison
0.32ms
0.33ms
0.36ms
0.33ms
 Deep Comaparison
4.45ms
4.15ms
4.47ms
4.35ms
Shallow Comparison은 해당 데이터의 주소값만 가져와 비교하므로 비교 대상이 복잡해지면 복잡해질 수록 Deep Comparison에 비해 빠른 속도를 가진다.
하나의 값을 비교할 때는 오히려 Deep Comparison이 빠르다.
해당 값을 가지고 있는 주소값을 가져오는 것은 직접 값을 참조하여 비교하는 것 보다 퍼포먼스 오버헤드가 존재하기 때문이다.

결론

리액트에선 왜 mutation 하지말라는거야?

개발 시 리액트를 사용하며 흔하게 state를 mutating 하는 상황이 발생할 수 있지만 이런 접근 방식은 리액트의 보이는 데이터 처리 접근방식에 반하는것→ state 변화가 리렌더링이다 를 기조로 리액트 팀은 기능 개발 및 최적화를 하고있다. - 리액트 공식문서 -
한마디로 state mutation 자체는 모터사이클 윌리와 비교할 수 있다. → 하란대로 하지 왜 이상하게 타는거야?
윌리 사진
모터사이클 제조사들은 모터사이클 앉아서 두손 잡고 타기 위해 수많은 테스트를 했고 이 테스트 들을 기반으로 제품을 디자인하고 퍼포먼스를 튜닝한다.
모터사이클 제조사들은 ABS와 TCS와 같은 안전을 위한 전자장비들이 적용되며 ECU 펌웨어 업데이트 또한 그에맞게 수행된다.
두바퀴 땅바닥에 닿아있고 정상적인 자세로 운전을 위한 업데이트와 기능 추가가 이루어진다.
근데 앞 바퀴 두개 다들고 시트가 아닌 엔진탱크위에 앉아서 주행을 한다면??? → 한마디로 “그렇게 타라고 만든게 아닌데 왜 그렇게 타냐ㅋㅋ?”
이런 위험 주행 시 사고가난다면 그 누구도 제조사를 탓하지 않는다. 당연히 라이더를 탓하지.. → “쟨 왜그렇게 탄거래?ㅋㅋ”
결론은 “제조사가 하지말란건 하지마라 좀.. 그러라고 만든거 아니라고..”
공식문서는 너무나도 친절하게도 나처럼 이상한 예를 들지않고 차근차근 이유를 들어가며 설명해준다. 리액트 개발자면 제발 리엑트 새로 나온 공식문서 꼭 읽읍시다! 제발! 제발!
DEEP DIVE에 Why is mutating state not recommended in React를 잘 읽어보자.
간단하게 요약하자면 “우리가 의도한건 이게 아니야. 그러라고 만든게 state가 아니라고”

immutable한 값 변화

비교 1 테스트는 백만개의 값 변화 시 속도 차이에 대한 테스트이다. → “어떤게 더 빠른가!” 에대한 차이를 극명하게 보기 위한 극단적 테스트 케이스
실제 프로덕트 개발 시 setState로 값 변화는 아무리 많아야 10개 이하 → state 값 변화에 대한 퍼포먼스 걱정 보다는 바뀐 state를 찾는 퍼포먼스 concern이 훨씬 더 크고 해당되는 case도 많다.

shallow comparion

state가 변경을 감지하는데 deep comaprison으로 리렌더링 되어야 할 컴포넌트 감지 시 시간 복잡도가 늘어나 문제가 생긴다.
state가 변경됨 → 변경된 값을 감지 해야함 → 모든 객체, 배열 내부의 property의 값을 비교하면 시간이 더 오래 걸린다.
그에 반해 shallow comparison으로 값을 비교할 경우 객체 내부의 모든 값 들의 변경 감지하는 대신 주소값 비교이므로 더 나은 퍼포먼스 제공한다.
새로 렌더링 되어야할 값들을 감지하는데 걸리는 시간단축

참조