← 블로그 목록

불변 자료구조가 비효율적으로 보이는데도 계속 쓰이는 이유

불변 자료구조는 매번 전체를 복사하는 비효율적인 방식처럼 보이지만, 실제 구현은 구조적 공유와 영속 자료구조를 바탕으로 훨씬 더 실용적으로 동작한다.

불변 자료구조가 비효율적으로 보이는데도 계속 쓰이는 이유

불변 자료구조가 비효율적으로 보이는데도 계속 쓰이는 이유

함수형 프로그래밍을 처음 접하면 불변 자료구조가 가장 먼저 낯설다. 값을 바꾸지 말고 새 값을 만들라고 하면, 직관적으로는 메모리를 낭비하는 방식처럼 보이기 때문이다.

하지만 실제 구현은 그렇게 단순하지 않다. 불변 자료구조가 실용적으로 쓰이는 이유는 전체 복사가 아니라 구조적 공유에 있다.


불변 자료구조는 “복사본을 매번 통째로 만든다”와 다르다

Clojure 공식 문서는 모든 컬렉션이 immutable and persistent, 즉 불변이고 영속적이라고 설명한다. 여기서 영속적이라는 말은 디스크에 영구 저장된다는 뜻이 아니라, 이전 버전을 유지하면서도 새 버전을 효율적으로 만들 수 있다는 뜻에 가깝다.

이 방식의 장점은 명확하다.

즉 불변성은 안전성을 위한 태도이고, 영속 자료구조는 그 태도를 너무 비싸지 않게 구현하는 기술이다.


핵심은 구조적 공유다

값 하나가 바뀌었다고 해서 전체 구조를 매번 새로 만드는 것은 비효율적이다. 그래서 불변 자료구조 구현체는 바뀌지 않은 부분을 공유한다. Clojure의 transient 문서도 내부적으로 배열을 바꾸고 나서 다시 불변한 값으로 되돌려 주는 식의 최적화를 설명한다.

이 말은 곧, 겉으로는 “새 값을 만든다”처럼 보여도 내부에서는 가능한 많은 부분을 재사용한다는 뜻이다. 그래서 불변성은 곧바로 “매번 O(N) 전체 복사”와 같지 않다.


불변성의 장점은 성능보다 먼저 예측 가능성에 있다

불변 자료구조가 계속 쓰이는 이유는 단지 빠르기 때문이 아니다. 더 중요한 이유는 예측 가능성이다.

이 특성은 규모가 커질수록 더 중요해진다. 작은 스크립트에서는 가변 구조가 더 편할 수 있지만, 여러 모듈과 스레드가 얽히는 코드에서는 불변성이 오히려 비용을 줄인다.


현실적인 코드는 불변성과 가변성을 섞어 쓴다

이 점도 중요하다. 불변 자료구조를 쓴다고 해서 내부 최적화나 일시적 가변성을 완전히 금지하는 것은 아니다. Clojure의 transients나 C#의 record처럼, 바깥 API에서는 불변성을 유지하되 내부에서 효율적으로 처리하는 식의 절충이 흔하다.

Microsoft의 C# record 문서도 레코드가 주로 불변 데이터 모델을 위해 설계되었다고 설명한다. 결국 중요한 것은 “절대 가변 금지”가 아니라, 어디까지를 안전하게 고정하고 어디에서만 바꿀 것인가를 정하는 일이다.


핵심 정리

불변 자료구조는 매번 전체를 통째로 복사하는 비효율적 방식처럼 보이지만, 실제로는 영속 자료구조와 구조적 공유를 바탕으로 훨씬 더 실용적으로 동작한다. 그래서 불변성이 중요한 이유도 단순한 성능 경쟁보다, 상태를 더 예측 가능하게 만들고 코드의 안전성을 높이는 데 있다.

결국 불변성은 “아무것도 바꾸지 말자”는 금욕이 아니다. 무엇이 언제 어떻게 바뀌는지 더 분명하게 만들자는 설계 선택이다.

참고 자료

← 목록으로
Related

함께 읽으면 좋은 글

함수형 프로그래밍게임 개발불변성
함수형 프로그래밍이 게임 개발에서 유용한 곳은 상태를 없애는 곳이 아니라 상태 변화를 분리하는 곳이다

함수형 프로그래밍은 게임 코드를 전부 다시 쓰라는 명령이 아니다. 불변 데이터와 순수 함수의 관점을 이용해 규칙 계산, 상태 전이, 서버 메시지 처리처럼 변화를 분리해야 하는 영역을 더 다루기 쉽게 만든다.

함수형 프로그래밍재귀순수 함수
함수형 프로그래밍에서 재귀가 중요한 이유는 루프를 금지해서가 아니라 상태를 드러내기 위해서다

함수형 프로그래밍에서 재귀는 루프의 대체재라기보다 상태 변화를 인자로 드러내는 방식에 가깝다. 특히 꼬리 재귀와 누산기 패턴을 이해하면 이 차이가 분명해진다.

Python코루틴Stackless Python
Stackless Python은 파이썬에 경량 태스크릿과 채널을 더한 구현이다

Stackless Python은 코루틴이 들어간 파이썬이라는 짧은 설명만으로는 부족하다. 태스크릿, 채널, 스케줄러를 통해 매우 가벼운 실행 단위를 다루는 별도 구현이며, PEP 342와 PEP 492가 정착시킨 오늘날의 `async`/`await`와는 다른 계보로 동시성을 메시지 전달과 작은 실행 주체의 협력으로 보게 만드는 관점을 보여 준다는 점을 정리한다.