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

[PYTHON] Multiprocessing Manager 객체를 통한 상태 공유 시 발생하는 3가지 오버헤드 해결 방법

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

Multiprocessing Manager 객체
Multiprocessing Manager 객체

 

 

파이썬의 Global Interpreter Lock(GIL)을 우회하여 CPU 집약적인 작업을 병렬로 처리하기 위해 우리는 multiprocessing 모듈을 사용합니다. 그중에서도 Manager 객체는 리스트(List), 딕셔너리(Dict)와 같은 복잡한 자료구조를 여러 프로세스가 공유할 수 있게 해주는 매우 편리한 도구입니다. 하지만 편리함 뒤에는 성능 저하라는 치명적인 '비용'이 숨어 있습니다. 본 포스팅에서는 Manager 객체를 사용할 때 발생하는 내부 메커니즘을 심층 분석하고, 실무에서 마주치는 성능 병목 현상을 해결하기 위한 구체적인 수치와 최적화 전략을 제시합니다.


1. Manager 객체의 동작 원리: 왜 느릴까?

Manager 객체가 데이터를 공유하는 방식은 Proxy(대리자) 패턴IPC(Inter-Process Communication)의 결합입니다. 일반적인 공유 메모리(Shared Memory)와 달리, Manager는 별도의 '서버 프로세스'를 띄우고 다른 워커 프로세스들이 네트워크 소켓을 통해 이 서버에 접근하는 방식을 취합니다.

  • 직렬화(Serialization): 데이터를 보낼 때 pickle 등을 이용해 바이트로 변환해야 합니다.
  • 통신 비용: 동일한 머신 내부일지라도 소켓 통신을 통한 데이터 전송 오버헤드가 발생합니다.
  • 동기화(Locking): 데이터 일관성을 위해 내부적으로 락을 관리하며, 이는 컨텐션(Contention)을 유발합니다.

2. Manager vs SharedMemory vs Raw Value 비교

상황에 맞는 최적의 도구를 선택하기 위해 각 방식의 특징을 데이터 구조와 오버헤드 관점에서 비교해 보았습니다.

비교 항목 Manager (List/Dict) Shared Memory (RawArray) Queue / Pipe
데이터 복잡성 높음 (중첩 구조 가능) 낮음 (고정 크기, 기본 타입) 중간 (직렬화 가능 객체)
접근 속도 매우 느림 (Proxy 오버헤드) 매우 빠름 (직접 참조) 중간 (이벤트 기반)
구현 난이도 매우 쉬움 보통 (동기화 직접 제어 필요) 쉬움
메모리 오버헤드 서버 프로세스 상주 비용 발생 매우 적음 데이터 복사본 발생

3. 실전 예제: 오버헤드 체감하기 (Sample Example)

다음은 일반적인 리스트와 Manager 리스트에 10,000개의 데이터를 삽입할 때의 성능 차이를 보여주는 코드입니다. 이 수치는 하드웨어 환경에 따라 다르지만, 통상적으로 10배에서 50배 이상의 차이를 보입니다.


import multiprocessing
import time

def update_list(target_list, n):
    for i in range(n):
        target_list.append(i)

if __name__ == "__main__":
    size = 10000
    
    # 1. 일반적인 리스트 (비공유)
    start = time.time()
    local_list = []
    update_list(local_list, size)
    print(f"일반 리스트 소요 시간: {time.time() - start:.4f}초")

    # 2. Manager를 통한 공유 리스트
    manager = multiprocessing.Manager()
    shared_list = manager.list()
    
    start = time.time()
    process = multiprocessing.Process(target=update_list, args=(shared_list, size))
    process.start()
    process.join()
    print(f"Manager 공유 리스트 소요 시간: {time.time() - start:.4f}초")

4. 성능 개선을 위한 3가지 전략적 해결 방법

방법 1: 데이터 접근 횟수의 최소화 (Granularity 조절)

Manager 객체에 빈번하게 접근하는 것은 최악의 성능을 초래합니다. 워커 프로세스 내부에서 지역 변수(Local Variable)로 작업을 완결한 뒤, 최종 결과물만 한 번에 Manager 객체에 업데이트하는 방식을 권장합니다.

방법 2: 고정 크기 데이터라면 SharedMemory 사용

Python 3.8부터 도입된 multiprocessing.shared_memory는 별도의 서버 프로세스 없이 OS 수준의 공유 메모리를 직접 참조하므로 Manager보다 수십 배 빠릅니다. 정수형 배열이나 바이트 데이터라면 반드시 이 방식을 고려하십시오.

방법 3: 읽기 전용 데이터는 상속 활용

상태를 수정할 필요 없이 공유만 해야 한다면, 리눅스/유닉스 환경의 fork 특성을 활용하여 부모 프로세스의 자원을 그대로 자식 프로세스가 읽게 함으로써 불필요한 Manager 생성을 억제할 수 있습니다.


5. 결론 및 요약

Manager 객체는 유연하지만 비용이 비싼 도구입니다. 대규모 트래픽이나 실시간 처리가 중요한 시스템에서는 Manager를 남용하기보다, 아키텍처 설계를 통해 프로세스 간 통신을 최소화하는 방향으로 선회해야 합니다.

출처 및 참고 문헌

  • Python Documentation: Multiprocessing — Process-based parallelism
  • High Performance Python (2nd Edition) - Micha Gorelick & Ian Ozsvald
  • CPython Source Code: multiprocessing/managers.py 분석
728x90