globals를 통한 반복문에서의 동적 변수 생성과 딕셔너리를 생성했을 때,
메모리 관리 측면에서의 차이점을 생각해 보자.
1. globals()의 메모리 관리
- globals()는 전역 변수 테이블을 직접 수정한다.
- 따라서 프로그램이 종료되기 전까지 메모리에서 해제되지 않는다.
- 따라서 가비지 컬렉션이 적용되지 않는다.
- 만약 반복문을 1000번 돌린다고 가정하면 1000개의 전역 변수가 생성되며, 메모리에 계속 남아 메모리 관리에 부담을 줄 수 있다.
- 또한 메모리에서 해당 변수명을 삭제하려면 하나씩 제거해야 하므로 매우 비효율적
- (변수 이름이 전역 네임스페이스에 추가되므로, 변수 관리가 어려워지고 이름 충돌 위험도 커진다.)
2. 딕셔너리의 메모리 관리
- 딕셔너리는 일반 객체이다. 따라서 특정 함수나 클래스(스코프) 내에서만 존재한다.
- 스코프가 끝나거나 더 이상 참조되지 않으면 자동으로 가비지 컬렉션이 수행.
- 메모리 해제가 쉽다.
data = {}
for i in range(100000):
data[f'var_{i}'] = i
# 필요 없을 때 메모리 해제
data.clear()
cf) 스코프란?
scope : 변수가 살아 있는 범위
지역변수의 경우, 함수 안에서만 살아 있고, 함수가 끝나면 사라진다.
전역변수의 경우, 프로그램이 종료될 때까지 살아있다.
cf) 가비지 컬렉션이란?
가비지 컬렉션(garbage collection)이란 필요 없어진 변수나 객체를 자동으로 메모리에서 제거하는 기능
파이썬은 변수나 객체의 참조 카운트를 세서, 더 이상 사용하지 않는 변수를 메모리에서 자동 정리해 준다.
3. 성능 비교
1) 메모리 사용량
메모리 사용량을 비교해 보자.
import sys
from pympler import asizeof
# 1. 초기 globals() 크기 측정
initial_globals_size = asizeof.asizeof(globals()) # 기존 전역 네임스페이스 크기
# 2. 전역 변수 100,000개 추가
for i in range(100000):
globals()[f'var_{i}'] = i
# 3. 새로 추가된 전역 변수 메모리 계산
current_globals_size = asizeof.asizeof(globals())
added_globals_size = current_globals_size - initial_globals_size # 추가된 변수의 크기만 계산
# 4. 딕셔너리 생성 및 크기 계산
dict_data = {f'var_{i}': i for i in range(100000)}
dict_size = asizeof.asizeof(dict_data)
# 5. 결과 출력
print(f"Added globals size: {added_globals_size} bytes")
print(f"Dictionary size: {dict_size} bytes")
딕셔너리의 저장 용량이 더 적다.
그 이유는 다음과 같다.
(1) 메타데이터 오버헤드
전역 변수는 네임스페이스에 등록되면서 변수의 이름, 타입, 참조 정보 등을 함께 관리해야 하기 때문에 필연적으로 메모리에 할당되는 크기가 클 수밖에 없다.
그러나 딕셔너리는 단순히 키, 밸류로 이루어져 있어 상대적으로 메모리를 차지하는 공간이 작다.
(2) 동적 해시 테이블 관리 비용
전역 name space는 hash table로 관리되므로, 해시 충돌을 줄이기 위해 추가적인 메모리 공간을 사용해야 한다.
(3) 가비지 컬렉션 제어
전역 변수는 프로그램 종료 시까지 메모리에서 해제되지 않는다. 따라서 불필요한 데이터 관리 비용이 발생한다.
2) 속도
import time
# globals() 성능
start = time.perf_counter()
for i in range(100000):
globals()[f'var_{i}'] = i
end = time.perf_counter()
print(f"globals time: {end - start:.4f} seconds")
# 딕셔너리 성능
start = time.perf_counter()
data = {f'var_{i}': i for i in range(100000)}
end = time.perf_counter()
print(f"dictionary time: {end - start:.4f} seconds")
이렇게 하면 보통 딕셔너리가 더 빠르다.
그 이유는 globals()는 네임스페이스 관리 비용이 추가되기 때문이다.
'Programming' 카테고리의 다른 글
Python 딕셔너리의 메모리 관리 구조(a.k.a hash table) (0) | 2024.12.28 |
---|---|
오버헤드(Overhead)란? (Feat 전역변수 vs 딕셔너리) (1) | 2024.12.28 |
Python에서 반복문을 활용한 동적 변수 생성 및 관리 1 (0) | 2024.12.28 |