본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 리스트와 튜플의 2가지 메모리 할당 방식 차이와 성능 최적화 방법

by Papa Martino V 2026. 3. 16.
728x90

리스트(List)와 튜플(Tuple)
리스트(List) 와  튜플(Tuple)

 

파이썬에서 데이터를 담는 가장 기본적인 컨테이너인 리스트(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()로 측정해보면 리스트가 더 크게 나타납니다. 이는 리스트가 두 가지 정보를 추가로 유지해야 하기 때문입니다.

  1. ob_item: 실제 객체들을 가리키는 포인터 배열의 주소
  2. 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를 사용하는 것이 진정한 의미의 메모리 최적화 방법입니다.

콘텐츠 출처 및 기술 참조

  • CPython Source Code 
  • Python Documentation
  • High Performance Python by Micha Gorelick & Ian Ozsvald.
728x90