언어 이름보다 실행 경계를 먼저 정한다
Python은 데이터 읽기·실험·모델 호출을 조합하는 데 유용하고 C++는 native 라이브러리와 자원·자료 배치를 직접 다루는 데 유용할 수 있다. 그러나 Python은 로봇에 쓸 수 없다거나 C++로 바꾸면 짧은 주기를 보장한다는 결론은 성립하지 않는다. 실제 실행은 라이브러리·운영체제·장치·자원 경쟁과 함께 결정된다. 이미 코드를 작성할 수 있는 학습자는 언어 기호를 다시 익히기보다 어느 경계에서 어떤 계약을 검증해야 하는지 정하자.
그림 M01.06-1. 각 칸은 서로 다른 역할/근거다. 아래 방향은 자료/처리의 연결이며 실제 성공을 자동으로 보장하지 않는다. 직접 제작한 개념도이며 본문의 조건과 함께 읽는다.
실험 프로세스는 영상 저장·모델/전처리 변경·목표 제안을 맡고, 시간 요구가 있는 소비 경로는 완성된 입력을 검증해 정한 제어 구조로 처리할 수 있다. 독립 안전 기능의 책임과 통신 단절 때의 대응도 따로 명시한다. 역할을 다른 프로세스나 언어로 나눴다는 사실만으로 늦은 목표·큐·공유 자원·공통 실패가 사라지지는 않는다. 동일 언어로도 책임을 분리할 수 있고, 다른 언어를 썼어도 같은 자원에 무제한 대기하면 기한을 놓친다. 이 단위는 장비 안전 기능을 구현·인증하지 않는다.
호출 반환과 실제 소비 준비를 구분한다
AI 연산의 시간은 어디서 시작하고 무엇이 끝났을 때 멈추는지에 따라 다르다. CPU가 가속기에 작업을 제출한 함수의 반환까지 잰 시간은 실제 연산·전송·소비 준비까지의 시간과 다를 수 있다. PyTorch CUDA semantics는 비동기 실행과 측정 시 완료 확인의 필요를 설명한다. 사용하는 API·stream·전송 방식의 동기화 계약을 확인해야 하며 모든 호출이 항상 비동기라는 뜻도 아니다.
합성 사건표에서 같은 비교 가능한 시계 기준으로 제출 시작이 100 ms, 호출 반환이 101 ms, 장치 완료가 113 ms, CPU 소비 자료 준비가 115 ms라고 하자. 제출 호출은 1 ms지만 시작부터 소비 준비까지는 15 ms다. 1 ms만 보고 10 ms 안에 새 결과를 쓸 수 있다고 판정하면 빠진 단계가 있다. 이것은 실제 CUDA 측정값이 아니라 경계를 읽는 계산이다. 실제 측정에는 장치 사건 시간과 호스트 시계의 비교 가능성, 완료 확인 범위·워밍업·부하 조건을 명시해야 한다.
버퍼도 함수가 돌아왔다고 즉시 재사용할 수 있다고 가정하지 않는다. 비동기 소비가 끝날 때까지 소유자가 살아 있고 기대한 표본이 바뀌지 않아야 한다. 완성된 snapshot을 소유하거나 대여 반환·완료 신호를 연결한다. dtype·shape·device와 복사/변환의 위치를 기록하면 자료 준비 시간과 추론 시간, 명령 생성 시간을 분리하기 쉽다. CPU/GPU 전송과 동기화를 소비 경로에 숨겨 두고 모델 함수 호출만 측정하면 실제 경계 지연을 빠뜨릴 수 있다.
평균이 빠른 실행과 기한을 지키는 실행
주기는 작업을 시작하려는 간격, 실행 소요는 실제 시작부터 끝까지의 시간이다. 마감 시각은 해당 작업이 끝나야 하는 시각이다. ROS2 실시간 설계 배경은 작은 평균 지연만으로 기한 내 처리를 보장할 수 없음을 설명한다. 이 문서는 특정 Jazzy 장비의 보장 인증서가 아니다.
동적 할당, 파일·네트워크 I/O, 잠금·큐 대기, 큰 복사, 예외/오류 경로와 스케줄링은 시간 변동을 만들 수 있다. native 연산으로 옮기거나 GIL의 영향을 피한 사실도 이 모든 경로의 상한을 증명하지 않는다. GIL은 일부 Python 실행의 동시성에 관련된 구현 조건이며 시스템 전체의 기한 규칙이 아니다. 실제 요구에 따라 자원 사용·부하·최악 경로와 마감 누락을 검토한다. time.sleep()도 시스템 스케줄 때문에 요청보다 오래 대기할 수 있으므로 정확한 재개 시각 보장으로 쓰지 않는다.
계획 주기가 10 ms인 합성 작업에서 계획 시작 20 ms, 실제 시작 21 ms, 끝 33 ms이면 실행 소요는 12 ms다. 마감이 계획 시작+10=30 ms라면 3 ms 늦었다. 실행 예산 초과 2 ms와 시작 지연 1 ms가 합쳐진 것이다. 평균 소요가 10 ms보다 작아도 이 누락은 남는다. 실행 끝이 실제 명령 적용이나 물리적 목표 도달까지 포함하는지도 기록해야 한다. 포함하지 않은 단계를 결과처럼 주장하지 않는다.
배열에서 명령까지 의미를 이어 붙인다
실험이 출력한 [0.2]는 관절 위치인지 변화량인지, rad인지 degree인지, 어느 관절인지 정해야 한다. 모델 입력의 축·dtype·device·단위·정규화와 출력의 행동 표현을 하나의 버전 계약으로 연결한다. native raw struct 메모리를 다른 프로세스에 복사하는 것만으로 패딩·수치 표현·주소·단위가 합의되지는 않는다. 명시한 필드 표현과 소비 검증이 필요하다.
다음은 교육용 메시지다. 모든 stamp는 같은 테스트 시계의 ms이며 absolute는 target 자체가 목표라는 뜻이다. frame은 이 예제의 관절 묶음 기준 이름이다.
{"version":1,"mode":"absolute","frame":"joint_set_A","unit":"rad","target":[0.2],"source_stamp_ms":1000,"command_stamp_ms":1040,"seq":17,"ttl_ms":40}
소비 계약은 target이 관절 A 하나의 유한한 rad 값이며 CPU 소비 측에서 float32 shape (1,)로 해석된 완성 snapshot이라고 정한다. JSON 숫자 자체가 float32 저장이나 장치 위치를 보장하지 않으므로 파싱 뒤 그 변환·범위·배치도 확인한다. 모델의 GPU 입력을 소비자가 그대로 읽을 수 있다고 가정하지 않는다. 자료와 메타데이터가 서로 다른 표본에서 섞이지 않도록 일관되게 전달한다.
적용 시각 1090 ms이면 명령 나이는 50 ms로 TTL 40 ms를 넘는다. 원본 관측 나이는 90 ms이며 별도 기준이 필요하다. 새 명령을 만들었다고 오래된 관측이 새 표본이 되지는 않는다. 시계가 다른 stamp는 바로 빼지 않으며 재시작 때의 epoch, 스트림 식별과 seq의 중복·역전·누락 규칙도 정한다. 유효 입력이 없을 때에는 이를 드러내고 사전에 정의된 대응을 적용한다.
최신 값 폐기는 메시지 의미에 따른다
절대 목표 0.1→0.2→0.3에서 마지막 값만 남기면 최종 목표 숫자는 0.3일 수 있다. 그러나 중간 경로·속도·접촉 조건까지 보존되지는 않는다. 소비자가 허용한 전이인지 검사해야 한다. 현재 0에 증분 +0.1을 세 번 더하는 계약이라면 모두 반영할 때 0.3, 하나만 남기면 0.1이 된다. 토글이나 순서 의존 절차도 최신 하나로 바꾸면 뜻이 달라질 수 있다.
따라서 최신 snapshot과 순서 의존 명령의 큐 정책을 구분한다. 단순히 큐 크기를 1로 줄이거나 실패 때 마지막 명령을 무조건 재사용하는 것은 보편적 해법이 아니다. 만료·단절·취소·중복과 재개 시 현재 상태를 확인하는 규칙을 적는다. Python 실험과 C++ 소비자를 나누는 설계의 성과는 언어별 우열이 아니라 이 경계가 해석·시간·수명과 실패까지 일관되게 검증되는지로 평가한다.