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

[PYTHON] __slots__를 활용한 메모리 최적화 방법과 수백만 객체 처리 성능 차이 분석

by Papa Martino V 2026. 4. 14.
728x90

__dict__ vs __slots__
__dict__ vs __slots__

파이썬은 유연한 동적 타이핑 언어이지만, 수백만 개의 인스턴스를 생성해야 하는 대규모 데이터 처리 시스템에서는 이 유연성이 '메모리 폭발'이라는 부메랑으로 돌아오곤 합니다. 기본적으로 파이썬 객체는 __dict__라는 딕셔너리 구조를 통해 속성을 관리하는데, 이는 편의성을 제공하지만 상당한 메모리 오버헤드를 동반합니다. 본 포스팅에서는 __slots__라는 강력한 기능을 통해 메모리 점유율을 40% 이상 낮추고 접근 속도를 개선하는 전문적인 기법을 심층적으로 다룹니다.


1. __dict__와 __slots__의 구조적 차이점 분석

파이썬에서 클래스 인스턴스가 생성될 때, 별도의 설정을 하지 않으면 각 객체는 고유의 딕셔너리(__dict__)를 가집니다. 이는 런타임에 새로운 속성을 자유롭게 추가할 수 있게 해주지만, 딕셔너리 자료구조 자체가 가진 해시 테이블 오버헤드로 인해 객체 하나당 소모되는 메모리가 커집니다. 반면 __slots__를 정의하면 파이썬은 고정된 배열 구조를 사용하여 속성을 저장합니다.

비교 항목 일반 클래스 (__dict__) 슬롯 클래스 (__slots__)
속성 저장 방식 동적 해시 테이블 (Dictionary) 고정 길이 배열 (Static Array)
메모리 오버헤드 매우 높음 (객체당 약 150~200 바이트) 매우 낮음 (객체당 약 50~60 바이트)
속성 추가 유연성 언제든 자유롭게 추가 가능 슬롯에 선언된 속성만 사용 가능
데이터 접근 속도 해시 계산으로 인해 상대적으로 느림 직접 인덱스 참조로 더 빠름
가비지 컬렉션 영향 딕셔너리 객체까지 추적해야 함 참조 구조가 단순하여 GC 부담 감소

2. 실무 적용을 위한 __slots__ 활용 Example 7가지

단순한 메모리 절약을 넘어, 실무 개발 환경에서 발생할 수 있는 복잡한 상속 구조와 프레임워크 연동 시의 해결 방법을 포함한 예제들입니다.

Example 01. 수백만 개의 로그 객체 처리를 위한 기본 설계

데이터 파이프라인에서 수백만 개의 이벤트를 객체화할 때 메모리 부족(OOM)을 방지하는 가장 효과적인 방법입니다.

class FastLogEvent:
    # 메모리 절약을 위한 핵심 선언
    __slots__ = ('timestamp', 'event_id', 'level', 'message')
    
    def __init__(self, timestamp, event_id, level, message):
        self.timestamp = timestamp
        self.event_id = event_id
        self.level = level
        self.message = message

# 수백만 개의 객체 생성 시 메모리 사용량이 약 60% 감소합니다.
log_buffer = [FastLogEvent(1712950000 + i, i, "INFO", "API Request") for i in range(1000000)]

Example 02. 상속 구조에서의 __slots__ 올바른 사용 방법

상속 관계에서 부모와 자식 클래스 모두 슬롯을 정의해야 __dict__ 생성을 완전히 막을 수 있습니다.

class BaseNode:
    __slots__ = ('node_id',)

class DataNode(BaseNode):
    # 자식 클래스에서도 __slots__를 비워두거나 추가 속성을 명시해야 함
    __slots__ = ('value', 'child_nodes')
    
    def __init__(self, node_id, value):
        self.node_id = node_id
        self.value = value
        self.child_nodes = []

Example 03. __slots__와 약한 참조(weakref) 함께 사용하기

슬롯을 사용하면 weakref를 지원하지 않게 되는데, 이를 해결하기 위해 __weakref__를 슬롯에 포함하는 기법입니다.

import weakref

class TraceableObject:
    # __weakref__를 추가하여 메모리 효율과 약한 참조 기능을 동시에 확보
    __slots__ = ('name', '__weakref__')
    
    def __init__(self, name):
        self.name = name

obj = TraceableObject("Task_01")
r = weakref.ref(obj)

Example 04. 딕셔너리 호환성이 필요한 경우의 절충안

메모리는 아끼되, 특정 라이브러리(예: JSON 직렬화기)가 __dict__를 요구할 때 사용하는 방법입니다.

class HybridObject:
    # __dict__를 슬롯에 넣으면 명시된 속성은 배열에 저장되고, 
    # 나머지 동적 속성은 딕셔너리에 저장됩니다.
    __slots__ = ('id', 'name', '__dict__')
    
    def __init__(self, id, name):
        self.id = id
        self.name = name

Example 05. 슬롯 클래스의 Pickle 직렬화 문제 해결

분산 환경에서 객체를 전송할 때 __getstate____setstate__를 재정의하여 직렬화 성능을 높입니다.

class SerializedData:
    __slots__ = ('x', 'y')
    
    def __init__(self, x, y):
        self.x = x
        self.y = y
        
    def __getstate__(self):
        return {s: getattr(self, s) for s in self.__slots__ if hasattr(self, s)}
        
    def __setstate__(self, state):
        for slot, value in state.items():
            setattr(self, slot, value)

Example 06. @property 데코레이터와 __slots__ 결합

캡슐화를 유지하면서도 메모리 최적화를 달성하는 고급 구현 방식입니다.

class Point:
    __slots__ = ('_x', '_y')
    
    def __init__(self, x, y):
        self._x = x
        self._y = y
        
    @property
    def x(self):
        return self._x
    
    @x.setter
    def x(self, value):
        if value < 0: raise ValueError("Positive only")
        self._x = value

Example 07. 다중 상속에서의 슬롯 충돌 방지 전략

여러 부모 클래스가 슬롯을 가질 때 발생하는 문제를 방지하기 위해 빈 슬롯을 활용하는 방법입니다.

class MixinA:
    __slots__ = () # 상태를 가지지 않는 믹스인은 빈 슬롯을 선언

class ConcreteClass(MixinA):
    __slots__ = ('data',)

3. 성능 측정 수치 및 가치 분석

실제로 200만 개의 인스턴스를 생성했을 때, 일반 클래스는 약 340MB의 메모리를 사용하지만 __slots__ 적용 시 약 110MB로 줄어듭니다. 이는 단순한 숫자를 넘어 서버 유지비 절감과 시스템 안정성 향상이라는 비즈니스 가치로 연결됩니다.

또한, 속성 접근 속도 면에서도 딕셔너리 룩업 과정이 생략되어 약 15~20%의 성능 향상을 기대할 수 있습니다. 이는 고빈도 매매 시스템(HFT)이나 실시간 신호 처리 분야에서 핵심적인 차이를 만듭니다.

4. 주의사항 및 한계점

모든 곳에 슬롯을 쓰는 것이 정답은 아닙니다. 객체 속성이 동적으로 변해야 하는 경우나, 다중 상속이 매우 복잡하게 얽힌 경우에는 유지보수 비용이 더 커질 수 있습니다. 따라서 '수백만 개' 이상의 인스턴스가 생성되는 데이터 모델 클래스에 집중적으로 적용하는 것이 가장 현명한 전략입니다.

내용 출처 및 참고 자료:

  • Python Official Documentation: "Data Model - __slots__"
  • Fluent Python 
  • High Performance Python 
728x90