13.5.2.3 공유 메모리 풀(Shared Memory Pool) 포인터 교환을 통한 제로 카피(Zero-Copy) 트랜잭션

13.5.2.3 공유 메모리 풀(Shared Memory Pool) 포인터 교환을 통한 제로 카피(Zero-Copy) 트랜잭션

단일 물리적 데몬(Single Host) 안에 파이프라인(Dataflow) 노드 여러 개를 구겨 넣고 엣지 추론을 태울 때, 시스템 성능의 급소를 쥐고 있는 것은 연산(CPU) 능력이 아니라 RAM 대역폭 버스의 메모리 복사 병목(Memory Copy Bottleneck)이다.
수백 메가바이트짜리 3D 포인트 클라우드 배열을 소스 노드에서 생성한 뒤 필터 노드로, 또다시 추론 노드로 건넬 때마다 C++ std::vector 로 복사본을 찍어낸다면, 불과 3개의 파이프 배관만 지나가도 로봇의 힙(Heap) 공간은 무의미한 복사본(Clone)들로 가득 차, 스로틀링과 함께 커널 뻗음(OOM)을 직면하게 된다.

본 절에서는 엣지컬(Edge-local) 통신 성능의 정체성을 결정짓는 제로 카피(Zero-Copy) 트랜잭션의 본질, 즉 데이터 실물을 나르는 몽매한 행위를 도살하고 오직 공유 메모리 객체 포인터(Shared Memory Pointer) 의 관할권(Ownership)만 교환하는 Zenoh-Flow의 인메모리 배관 철학을 확립한다.

1. 딥 카피(Deep Copy) 파이프의 광기와 L3 캐시(Cache) 파멸

만약 파이프라인 프레임워크가 무식하게 데이터를 복사(Deep Copy)에 의존하고 있다면 어떻게 될까?
A 노드 스레드에서 생성된 10MB짜리 점군 데이터를 B 노드로 보낼 때, 미들웨어는 RAM의 1번 번지 구역(10MB)을 2번 번지(10MB)로 똑같이 memcpy() 해버린다.

이 행위는 단순한 용량 증가를 넘어 캐시 스래싱(Cache Thrashing) 이라는 은밀한 내부적 재앙을 터뜨린다.
최신 CPU 코어 옆에 붙은 초고속 L2/L3 캐시는 1번 구역 캐싱을 끝내놓고 B 노드의 연산을 기다리고 있었다. 그런데 갑자기 데이터가 공간을 점프하여 2번 번지로 이동해 버렸다. CPU는 캐시 미스(Cache Miss)를 내며 그 고약하게 느려 터진 진짜 RAM 기판에서 2번 번지 데이터를 또다시 자신의 캐시 테이블로 끌어 올리느라 수천 데드-클럭(Dead clocks)을 허비하게 된다.

2. Shared Memory Pool의 개방과 권리증(Pointer)만의 이동

이 원초적인 대역폭 학살극을 뿌리 뽑기 위해 Zenoh-Flow 는 파이프라인 노드 간(동일 머신 내) 통신 링킹(Link)에서 실물 바이트 전송이라는 개념 자체를 완전히 말살시킨다.

대신 거대한 공용 도화지(Shared Memory Pool)를 열어놓고 다음과 같이 룰을 지배한다.

  1. 할당(Allocation) : 소스(A) 노드가 카메라 드라이버에서 이미지를 받을 때, 자기 스레드의 로컬 메모리에 담지 않는다. 데몬이 제공해 주는 글로벌 공유 메모리 풀 구역으로 바로 포인터를 던져 메모리를 잡아버린다. (실물은 이 거대 수영장 안에 고정 안착됨)
  2. 포장(Encapsulation) : 이 데이터가 어디에 잡혀 있는지 가리키는 얇은 8바이트 메모리 주소표(Reference Counting Pointer, Arc 혹은 shared_ptr 형태)를 뜯어내어 프레임워크용 가벼운 캡슐(zflow::Data)의 품에 소집한다.
  3. 사출(Produce) : A 노드는 10MB 실물을 보내지 않는다! 오직 저 가벼운 ‘권리증 쪼가리(Pointer)’ 1개를 큐 배관으로 밀어 넘긴다.
  4. 소비(Consume) : 수신단인 B 노드 스레드는 큐에서 이 권리증 쪼가리만 주워 들고, 공유 수영장의 고정된 주소로 점프해 들어가 동일한 10MB 실물에 즉각적인 읽기/가공 행위(In-place Operation)를 꽂아 넣는다.
// [Zenoh-Flow C++ 노드 간 포인터 이송 기반 Zero-Copy 사출 런북]

void produce_to_filter(OutputPort& port) {
    // 1. 공용 공간 할당체(공유 포인터)를 물고 있는 데이터 덩어리 창출
    auto massive_payload = std::make_shared<std::vector<uint8_t>>(10 * 1024 * 1024); // 10MB
    fill_data(*massive_payload); // 실물 탑재 완료!
    
    // 2. 엄청난 파괴력의 Zero-Copy 캡슐화! 
    // 데이터는 꼼짝하지 않고 포인터 권리만 빌려(borrow) 젠다
    auto zflow_capsule = zf::Data::borrow(massive_payload.get(), massive_payload->size());
    
    // 3. 파이프 배관으로는 10MB가 아니라, 수 바이트 포인터만 던져짐 (O(1) 통신 스피드)
    port.send(std::move(zflow_capsule)); 
}

3. 포인터 락(Lock) 관조와 O(1) 상수 시간(Constant Time)의 위엄

이 공유 메모리 권한 교환(Reference Handoff) 메커니즘이 확립된다면 파이프라인의 전송 지연 그래프(Latency Chart)는 경이로울 지경의 수평선, 즉 O(1)의 상수 시간(Constant Time) 곡선을 그리게 된다.
카메라 데이터가 10MB에서 100MB로 열 배 커졌다 한들 네트워크/루프백 딜레이 구간 자체는 커지지 않는다. 전송할 바이트는 여전히 8바이트의 꼴랑 주소표(Pointer) 하나이기 때문이다.

소켓을 열어 루프백(127.0.0.1)으로 데이터를 복사 포워딩한 뒤 잘난 체하는 아마추어 소프트웨어 엔지니어링은 하드 리얼타임 생태계에서는 즉결 처형 감이다.
파이프라인의 배관을 물리적 복사가 아닌 거대 인메모리(Pool) 공간의 소유권(Ownership) 등기부등본 교환소로 강제 전환하는 것, 이것이야말로 CPU L3 캐시의 온기를 살려두고 엣지 추론 모델에 최상단의 프레임 레이트(Frames per Second)를 멱살 쥐고 헌납하는 궁극의 시스템 최적화(Transaction)다.