
파이썬에서 데이터를 담는 가장 기본적인 컨테이너인 리스트(List)와 튜플(Tuple)은 겉보기에는 비슷해 보이지만, 내부적으로 메모리를 다루는 철학은 완전히 다릅니다. 단순히 "리스트는 수정 가능하고, 튜플은 불변이다"라는 수준을 넘어, 대규모 데이터를 다룰 때 왜 리스트가 더 많은 메모리를 점유하는지, 그리고 초과 할당(Over-allocation)이라는 메커니즘이 성능에 어떤 영향을 미치는지 전문가의 시각에서 심층 분석합니다.
1. 가변성과 불변성이 만드는 메모리 구조의 차이
파이썬의 리스트는 가변(Mutable) 객체입니다. 이는 프로그램 실행 중에 요소의 추가나 삭제가 빈번하게 일어날 수 있음을 의미합니다. 반면 튜플은 불변(Immutable) 객체로, 한 번 생성되면 크기나 내용이 절대 변하지 않습니다. 이 본질적인 차이가 '정적 할당'과 '동적 할당'이라는 전략적 차이를 만듭니다.
- 튜플 (Tuple): 생성 시점에 필요한 정확한 크기의 메모리만 할당합니다. 군더더기 없는 '딱 맞는 옷'과 같습니다.
- 리스트 (List): 새로운 요소가 추가될 것을 대비하여 실제 데이터보다 더 큰 메모리 공간을 미리 예약합니다. 이를 초과 할당(Over-allocation)이라고 합니다.
2. 리스트의 비밀: 초과 할당(Over-allocation) 메커니즘
CPython의 리스트 구현부를 살펴보면, 요소를 append() 할 때마다 매번 메모리를 새로 할당받지 않습니다. 매번 메모리를 재할당하고 기존 데이터를 복사하는 작업은 $O(n)$의 비용이 들기 때문입니다. 파이썬은 이를 방지하기 위해 Amortized Constant Time [$O(1)$]을 목표로 여유 공간을 미리 확보합니다.
표: 리스트와 튜플의 내부 메모리 관리 방식 비교
| 비교 항목 | 리스트 (List) | 튜플 (Tuple) |
|---|---|---|
| 메모리 할당 전략 | 동적 초과 할당 (Over-allocation) | 정적 고정 할당 (Fixed-size) |
| 객체 크기 (Overhead) | 더 큼 (여유 슬롯 포함) | 더 작음 (데이터 전용) |
| 데이터 추가 성능 | 빠름 (미리 확보된 공간 활용) | 불가능 (새 객체 생성 필요) |
| 메모리 재사용 | 가비지 컬렉션에 의존 | 작은 튜플은 내부적으로 재사용(Caching) |
3. 왜 리스트는 튜플보다 무거운가?
동일한 요소를 담고 있더라도 sys.getsizeof()로 측정해보면 리스트가 더 크게 나타납니다. 이는 리스트가 두 가지 정보를 추가로 유지해야 하기 때문입니다.
- ob_item: 실제 객체들을 가리키는 포인터 배열의 주소
- allocated: 현재 메모리에 할당된 총 슬롯의 개수
튜플은 고정 크기이므로 allocated 정보를 저장할 필요가 없으며, 객체 구조 자체가 훨씬 단순하여 시스템 리소스를 적게 소모합니다.
4. Sample Example: 메모리 점유율 측정 및 비교
실제 코드를 통해 리스트가 어떻게 메모리를 단계적으로 확장하는지 확인해 보겠습니다.
import sys
# 1. 동일한 데이터를 가진 리스트와 튜플 비교
my_list = [1, 2, 3, 4, 5]
my_tuple = (1, 2, 3, 4, 5)
print(f"List size: {sys.getsizeof(my_list)} bytes") # 약 104 bytes (환경별 상이)
print(f"Tuple size: {sys.getsizeof(my_tuple)} bytes") # 약 80 bytes
# 2. 리스트의 동적 할당 과정 관찰
growth_list = []
last_size = sys.getsizeof(growth_list)
print("\n--- 리스트 성장 기록 ---")
for i in range(20):
growth_list.append(i)
current_size = sys.getsizeof(growth_list)
if current_size != last_size:
print(f"요소 {i+1}개 추가 시점: 메모리 재할당 발생! ({current_size} bytes)")
last_size = current_size
위 코드를 실행하면 리스트에 요소가 추가될 때 특정 구간에서 메모리 크기가 '점프'하는 것을 볼 수 있습니다. 이것이 바로 CPython의 list_resize 함수가 작동하는 방식입니다.
5. 성능 최적화를 위한 실무 해결 전략
데이터 과학이나 백엔드 시스템 구축 시, 수백만 개의 행을 처리해야 한다면 다음의 전략을 고려해야 합니다.
- 변경이 없다면 무조건 튜플: 딕셔너리의 키로 사용할 수 있을 뿐만 아니라, 메모리 효율과 읽기 속도 면에서 압도적입니다.
- 크기가 고정된 리스트: 리스트를 사용해야 하지만 크기를 미리 알고 있다면,
[None] * 1000과 같이 미리 할당하여 초과 할당에 의한 성능 저하를 방지할 수 있습니다. - 대량의 수치 데이터: 파이썬 리스트 대신
array모듈이나NumPy를 사용하는 것이 진정한 의미의 메모리 최적화 방법입니다.
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] Global Interpreter Lock이 threading 스케줄링에 주는 3가지 영향과 성능 해결 방법 (0) | 2026.03.17 |
|---|---|
| [PYTHON] 효율적 데이터 스트리밍을 위한 비동기 제너레이터 활용 방법과 3가지 실무 해결 사례 (0) | 2026.03.17 |
| [PYTHON] 딕셔너리 해시 충돌 해결 방법과 3.6 버전 이후 순서 보장의 2가지 핵심 원리 (0) | 2026.03.16 |
| [PYTHON] weakref 모듈 사용 방법과 순환 참조 2가지 문제 해결 및 성능 차이 분석 (0) | 2026.03.16 |
| [PYTHON] 제너레이터가 스택 프레임을 유지하는 3가지 방법과 메모리 효율 해결 원리 (0) | 2026.03.16 |