13.5.3.2 가비지 컬렉터(GC) 간섭 방지 및 GIL 블로킹 우회를 위한 Python 비동기 텐서 연산 프로그래밍

13.5.3.2 가비지 컬렉터(GC) 간섭 방지 및 GIL 블로킹 우회를 위한 Python 비동기 텐서 연산 프로그래밍

Zenoh-Flow 환경의 파이프라인에서 가장 치명적이고 빈번하게 터지는 지뢰는 Python으로 작성된 AI 오퍼레이터(Operator) 노드다.
인터프리터 언어라는 태생적 한계로 인해 작동하는 가비지 컬렉터(Garbage Collector, GC)의 백그라운드 청달 작업과 전역 스레드 제어기(Global Interpreter Lock, GIL)는, 초당 60프레임으로 쏟아지는 자율주행 텐서 데이터를 소화하려는 파이프라인의 멱살을 잡고 스로틀링(Throttling)을 유발한다.

본 절에서는 딥러닝/수학적 연산을 파이프라인에 이식할 때, RAM 증폭에 따른 GC 발작을 사전 회피하고, GIL을 찢어 발겨 비동기 텐서 연산의 병렬 구동을 끌어내는 Python 런타임 튜닝 런북을 설파한다.

1. 오브젝트 재활용(Object Pooling)과 무자비한 GC 봉쇄

파이썬의 가비지 컬렉터는 힙(Heap) 메모리에 떠도는 Reference Count 0 짜리 찌꺼기 객체들을 주기적으로 쓸어 담는다(Sweep).
만일 AI 오퍼레이터 노드의 on_data_received 콜백 함수 내부에서 매 틱(tick)마다 새로운 np.zeros(1024) 같은 임시 빈 객체를 malloc 해대고 처리가 끝난 뒤 그냥 버린다면(Local Variable), 1초에 60번씩 거대한 메모리 할당과 유기가 반복된다.

이때 임계치에 도달한 파이썬 GC 엔진은 모든 연산 스레드 권한을 정지시키는 “Stop-The-World” 발작을 일으켜 메모리를 청소한다. 로봇은 0.5초간 시야를 잃고 눈먼 장님으로 질주하게 된다.

이를 박살 내기 위해 파이프라인 개발자는 오브젝트 풀(Object Pool) 패턴을 강제하여 GC가 일할 거리 자체를 말살해야 한다.

# [Zenoh-Flow 파이썬 노드의 GC 발작 원천 봉쇄 런북]
import gc
import numpy as np

class VisionTensorOperator:
    def __init__(self):
        # 1. 끔찍한 간섭을 막기 위해 파이썬 내장 GC의 자율 가동을 하드웨어 레벨에서 마비시킨다
        gc.disable() 
        
        # 2. 런타임 중에 만들고 버리지 않도록, 부팅 단계에서 
        # 최대 사이즈의 배열 뼈대(Tensor Object)를 미리 할당해 둔다.
        self.pre_allocated_buffer = np.zeros((1, 3, 1080, 1920), dtype=np.float32)

    def on_data_received(self, raw_data):
        # 3. 객체를 생성하지 않고, 미리 파놓았던 고정 수영장(buffer)에 In-place로 덮어쓰기만 한다!
        np.copyto(self.pre_allocated_buffer, decode(raw_data))
        
        # 4. 연산 수행 (GC는 켤 코딩 내내 단 한 번도 호출되지 않는다)
        result = run_yolo(self.pre_allocated_buffer) 
        send_out(result)

2. GIL(Global Interpreter Lock) 분쇄와 C-확장 텐서 호출

메모리를 억제했다면 다음 적은 파벌주의인 GIL(Global Interpreter Lock)이다.
단일 Python 데몬 프로세스 안에서 4개의 파이프라인 큐가 동시에 데이터를 찔러 넣어 스레드를 4개 띄우더라도, 결국 GIL이라는 문지기 때문에 한 번에 파이썬 바이트코드를 실행할 수 있는 스레드는 단 1개로 제한된다.

이 한계를 회피하기 위해선, CPU를 갈아먹는 연산(Heavy Compute)은 파이썬 인터프리터 밖으로 내동댕이쳐야 한다. 파이썬 노드는 오직 ’데이터의 배율’과 ’C++ 텐서 코어의 호출(Invoke)’만을 지휘하는 C-레벨 라우터로 강등시켜라.

PyTorch 나 Numba 같은 프레임워크는 내부 C++ 코어(libtorch)를 구동할 때 얌전하게 GIL을 해제(Release)한다.
오퍼레이터가 torch.matmul() 연산을 수행중일 땐 파이썬 인터프리터의 GIL 락이 열려 있으므로, 파이프라인의 다른 백그라운드 네트워크 수신 스레드들이 방해받지 않고 Zenoh 큐에서 데이터 블록을 자유롭게 꺼내어 메모리에 적재(I/O Multiplexing) 할 수 있게 된다.

3. 멀티프로세싱 데몬 인스턴스(Multi-Process Instances)의 강제

C-바인딩 최적화로 텐서 연산을 우회했음에도, JSON 파싱이나 문자열 비교 같은 순수 파이썬 로직이 많아 어쩔 수 없이 GIL이 병목을 일으킬 때는, 시스템 아키텍트는 야믈(YAML) 디스크립터 선언의 권력으로 돌아가야 한다. 파이썬의 태생적 질병을 코딩으로 고치려 들지 마라.

Zenoh-Flow 선언부(YAML)에서 해당 파이썬 어플리케이션 노드(Operator)의 인스턴스(Instances) 속성을 강제로 늘려버린다.

operators:
  - id: "python_fusion_node"
    uri: "file:///opt/zenoh-flow/python_op.py"
    # [인프라스트럭처 권력] 프로세스 4개를 병렬로 하드포크(Hard Fork) 시킨다!
    # 각 프로세스는 자신만의 독립된 GIL 환경과 메모리 파이프(IPC)를 물고 돌아간다.
    instances: 4 

이렇게 하면 4개의 복제된 파이썬 프로세스가 거대한 단일 Zenoh 큐 밸브에 달라붙어 일감을 탈취(Work-Stealing)하기 시작한다. 결국 파이프라인 엔진은 “가비지 컬렉터의 메모리 폭동“과 “GIL의 직렬화 병목“을 회피 및 분산 압살 전술만으로 억눌러 버리고, 느려 터졌던 AI 노드가 마치 C++ 바이너리처럼 거침없이 트래픽을 소화해 내도록 조련을 완성하게 된다.