9.5.1 C 언어 환경의 성능 최적화 기법

9.5.1 C 언어 환경의 성능 최적화 기법

C 언어 기반의 시스템 프로그래밍 환경에서 통신 성능의 가장 큰 제약 요소는 역설적이게도 운영체제(OS; Operating System)의 커널 개입성(Kernel Intervention)이다. 아키텍트가 Zenoh 통신 알고리즘을 극한으로 최적화하여 구현하더라도, 커널 스케줄러(Kernel Scheduler)에 의한 강제적인 컨텍스트 스위칭(Context Switching)이 발생하면 파이프라인의 연산 사이클과 캐시 지역성(Cache Locality)이 파괴되어 시스템 틱(Tick) 단위의 비결정론적인 지연이 유발된다. 본 장에서는 운영체제의 간섭을 배제하고 CPU 자원을 배타적으로 통제하는 저수준(Low-Level) 최적화 기법을 명세한다.

1. 락-프리(Lock-Free) 큐 구조 연동 및 지연 시간(Latency) 최소화

초고주파수의 텔레메트리 데이터를 다루는 환경에서 pthread_mutex_t와 같은 잠금(Lock) 기반의 동기화 기법을 사용하면, 리소스 경합(Resource Contention) 시 스레드가 블로킹(Blocking) 커널 루틴으로 진입하게 되어 시스템 전체의 지터(Jitter)를 유발한다.

1.0.1 런북(Runbook): 락-프리 링 버퍼(Lock-Free Ring Buffer) 전술

다수의 Zenoh 수신 콜백 스레드(생산자)가 비동기적으로 데이터를 기록하고, 단일한 메인 제어 스레드(소비자)가 데이터를 순차적으로 읽어 들이는 MPSC(Multi-Producer Single-Consumer) 아키텍처를 도입하라.

graph LR
    classDef Thread fill:#e3f2fd,stroke:#1565c0,stroke-width:2px;
    classDef Queue fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px;
    classDef Atomic fill:#fff3e0,stroke:#e65100,stroke-width:2px;

    subgraph "Zenoh Subscriptions (Publishers to Queue)"
        P1[Zenoh Rx Thread 1]
        P2[Zenoh Rx Thread 2]
        P3[Zenoh Rx Thread 3]
    end

    subgraph "Lock-Free Ring Buffer"
        AtomicHead((Atomic<br>Head Index))
        Ring[Circular Array]
        AtomicTail((Atomic<br>Tail Index))
    end

    subgraph "Main Logic (Consumer)"
        C1[Robot Control Thread]
    end

    P1 -->|Fetch & Add| AtomicHead
    P2 -->|Fetch & Add| AtomicHead
    P3 -->|Fetch & Add| AtomicHead

    AtomicHead -->|Write Payload| Ring
    Ring -->|Read Payload| AtomicTail
    AtomicTail -->|Update Index| C1

    class P1,P2,P3,C1 Thread;
    class Ring Queue;
    class AtomicHead,AtomicTail Atomic;

하드웨어 수준의 아토믹(Atomic) 연산을 이용하는 C11 기준의 링 버퍼 구현 아키텍처는 다음과 같다.

#include <stdatomic.h>
#include <zenoh.h>
#include <string.h>

#define RING_SIZE 1024

typedef struct {
    char data[32]; // 정적 페이로드 저장소
} Packet;

Packet ring_buffer[RING_SIZE];
atomic_size_t ring_head = 0; // 생산자 쓰기 인덱스
atomic_size_t ring_tail = 0; // 소비자 읽기 인덱스

// [Zenoh Callback: 다중 스레드에서 비동기적으로 동시 호출됨]
void lockfree_callback(const z_sample_t* sample, void* ctx) {
    // 1. 블로킹 없이 원자적(Atomic) 연산을 통해 헤드 인덱스를 증가 (CPU 레벨 명령어)
    size_t current_head = atomic_fetch_add(&ring_head, 1) % RING_SIZE;
    
    // 2. 할당된 버퍼 인덱스에 수신 페이로드 복사 (최대 32바이트 기준 가정)
    memcpy(ring_buffer[current_head].data, sample->payload.start, sample->payload.len);
    ring_buffer[current_head].data[sample->payload.len] = '\0';
}

// [Main Control Loop: 단일 소비자 스레드가 자원을 배타적으로 순회 처리]
void* robot_control_loop(void* arg) {
    while(1) {
        size_t h = atomic_load(&ring_head);
        size_t t = atomic_load(&ring_tail);
        
        // 읽지 않은 가용 데이터 큐 순회 및 소비
        while (t < h) {
            size_t idx = t % RING_SIZE;
            printf("Lock-free Processing: %s\n", ring_buffer[idx].data);
            t++;
        }
        atomic_store(&ring_tail, t); // 읽기 완료 후 테일 인덱스 갱신
        
        // 반복문에 의한 CPU 코어 점유 100% 현상을 방지하기 위한 최소한의 백오프(Backoff) 시간 부여
        usleep(100); 
    }
}

이러한 하드웨어 명령어 기반의 atomic_fetch_add 동기화 구조는 커널 레벨의 컨텍스트 스위칭을 원천 회피함으로써 기존 Mutex 기반 접근법보다 비약적인 명령 처리량(Instruction Throughput) 성능 최적화를 달성한다.

2. CPU 친화도(Processor Affinity) 및 실시간 스케줄링(Real-Time Scheduling) 적용

기본적인 리눅스(Linux) 스케줄러 정책인 SCHED_OTHER는 다중 스레드 간의 공정성(Fairness)을 중시하므로, 스레드가 주기적으로 각기 다른 코어로 마이그레이션(Migration)되며 코어 내부 L1 및 L2 캐시 메모리의 스래싱(Cache Thrashing) 현상을 유발한다. 시스템 엔지니어는 핵심 트래픽을 관장하는 데이터 플레인 스레드에 특정 프로세서 코어를 배타적으로 할당하고, 스케줄링 우선순위를 최고 수준으로 강압해야 한다.

2.0.1 런북(Runbook): POSIX 코어 격리(Core Isolation) 및 우선순위 상향 전술

로보틱스 구동 모듈과 통신 모듈을 초기화하는 C 코드 단계에 다음의 POSIX 표준 제어 설정을 적용하라.

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <pthread.h>
#include <sched.h>
#include <stdio.h>

// 스레드별 프로세서 단위 친화도 할당 및 실시간 정책 최적화 함수
void optimize_thread_for_robotics(pthread_t thread_id, int core_num) {
    // 1. CPU 코어 강제 할당 (Affinity Binding)
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(core_num, &cpuset); // 목표 코어 매핑

    int rc = pthread_setaffinity_np(thread_id, sizeof(cpu_set_t), &cpuset);
    if (rc != 0) {
        printf("[오류] 프로세서 친화도 할당에 실패하였습니다.\n");
    }

    // 2. 리눅스 실시간(Real-Time) 스케줄링 정책 적용 (FIFO 방식)
    // 선점형 스케줄링을 차단하여 코어 자원의 배타적 프로세스 점유율을 극대화
    struct sched_param param;
    param.sched_priority = 99; // 최고 우선순위 범위 지정 (1~99)
    
    // 해당 시스템콜 호출은 CAP_SYS_NICE 권한 혹은 루트(Root) 접근 권한이 절대적으로 요구됨
    rc = pthread_setschedparam(thread_id, SCHED_FIFO, &param);
    if (rc != 0) {
        printf("[오류] 실시간 RT 스케줄링 정책 설정에 실패하였습니다. (권한 상향 필요)\n");
    }
}

시스템 구동 초기화 시퀀스에서 메인 제어 스레드와 Zenoh 네트워킹 수신 스레드에 이 커널 제어 규약을 즉각 적용하라. 이를 통해 OS 커널 루틴의 무작위 개입으로 인하여 모터 제어 주기가 지연되거나 물리적인 로봇 조향에 지터(Jitter)가 발생하는 현상을 근본적으로 차단할 것을 체계적으로 권고한다. (단, 물리 코어 수를 초과하여 FIFO 우선순위 기반 스레딩을 광범위하게 배포할 경우 운영체제 전반의 스케줄링 락업(Lock-up) 마비를 야기할 위험이 존재하므로 코어 매핑 토폴로지 설계를 치밀하게 검증해야 한다.)