Devin.KR

이벤트 지연과 호스트 시간

100분 안팎

학습 목표

C 단조 시계와 Python perf_counter로 조건·반복수·최대 지연을 기록합니다.

개념

측정할 사건의 양 끝을 정합니다

센서 기록기가 빠르다는 말만으로는 제품의 반응을 설명할 수 없습니다. 버튼을 누른 뒤 LED가 바뀌기까지, 타이머 예정 시각부터 샘플을 큐에 발행하기까지, 실행 파일이 끝나기까지는 서로 다른 구간입니다. 이번 레슨은 양 끝 사건과 단위를 먼저 정하고, 가상 지연 집계와 호스트 실행 시간을 서로 다른 열에 기록하는 연습입니다. 가장 짧게 나온 숫자를 고르는 대신 무엇을 재었는지 설명할 수 있어야 합니다.

로컬 실습은 C11 컴파일러·make·Python 3 표준 라이브러리를 사용합니다. 압축을 새 폴더에 풀어 Makefile이 있는 위치에서 실행합니다. gcc --version으로 맥의 gcc 명령이 Apple Clang인지 확인하고 Python 버전도 기록합니다. 첫 실행의 starter는 컴파일 후 일부 조건 검사에서 실패하도록 준비되어 있습니다. solution은 같은 테스트를 통과합니다. 브라우저 실습은 한 줄의 입력을 받고 한 줄의 결과만 출력합니다.

이벤트 예정 시각을 due, 관찰한 완료 시각을 done이라고 적습니다. 두 값은 같은 가상 밀리초 시계를 사용합니다. 지연은 done에서 due를 뺀 값입니다. 센서 측정 timestamp와 타이머 due는 다른 사건이므로 바꾸어 쓰면 예정 시각을 놓친 시간이 사라집니다. 버튼 반응의 출발점은 원시 핀 변화이며 디바운스 시간까지 포함합니다. 시작점에 이미 확정된 버튼 사건을 넣으면 사용자가 기다린 일부 시간을 누락합니다.

가상 시간은 마감 판정에 사용합니다

Latency 구조체는 count·max_ms·missed를 가집니다. 입력 하나가 유효할 때 count를 올리고 지연이 기존 max_ms보다 클 때 최대값을 갱신합니다. 지연이 budget을 초과할 때만 missed를 올립니다. 20ms 제한에서 20은 통과하고 21은 위반입니다. 평균만 남기면 드물게 긴 지연을 숨길 수 있으므로 최대값과 위반 수를 함께 보관합니다. 샘플 수가 0이면 최대값0은 관찰 없음이지 즉시 응답했다는 의미가 아닙니다.

가상 시각은 uint32_t여서 최댓값 다음에 0으로 돌아갑니다. unsigned 차이를 계산하면 짧은 래핑 구간을 표현할 수 있습니다. due가 UINT32_MAX-2이고 done이2이면 차이는5입니다. 그러나 무제한 시간 차이를 허용하지 않습니다. 이번 계약은 두 사건의 실제 간격이 INT32_MAX 이하라는 가정을 사용하고, 계산한 차이가 그보다 크면 잘못된 순서 또는 관찰 범위 위반으로 거부합니다. 긴 미관찰 구간을 실제 순서로 복원하는 기능은 없습니다.

유효하지 않은 입력은 통계를 바꾸지 않습니다. NULL 포인터, done이 due보다 앞선 일반 구간을 넣고 반환0인지 확인합니다. r의 count를 먼저 증가시킨 뒤 입력 검사를 하면 오류 입력도 정상 샘플처럼 집계됩니다. 검사 후 갱신이라는 순서를 코드에 드러냅니다. 반환값을 무시하는 호출부도 수정 대상입니다. 통계 객체의 수명은 관찰 구간과 같도록 관리하고 새 시험 전에 초기화합니다.

호스트의 경과 시간을 따로 잽니다

C clock_gettime에 CLOCK_MONOTONIC을 지정하여 앞뒤 timespec을 읽습니다. 이는 달력 시각을 보고하기 위한 코드가 아닙니다. tv_sec를 나노초로 환산한 뒤 tv_nsec를 더하고 종료값에서 시작값을 뺍니다. 두 필드를 그냥 더하면 초와 나노초가 섞입니다. 64비트로 변환한 뒤 곱해 중간 계산의 폭을 확보합니다. 호출 반환값이 실패이면 perror로 원인을 남기고 그 측정값을 버립니다.

실습 clock.c는 같은 루프를 워밍업1회 뒤100회 실행합니다. 각 구간에는 루프 실행과 시계 읽기 비용이 일부 포함됩니다. printf는 구간 밖에서 한 번 실행합니다. volatile 누산기는 작업 제거를 억제하는 이 예제의 장치일 뿐 앞 모듈의 큐 동기화를 대체하지 않습니다. 실제 센서 처리 시간을 얻으려면 관심 작업을 해당 구간에 넣고 결과가 사용되는지 확인해야 합니다. 벤치마크 루프의 숫자를 MCU 명령 비용으로 환산하지 않습니다.

Python measure.py는 time.perf_counter로 실행 파일의 프로세스 경과 시간을 잽니다. 자식 생성·스케줄링·C 내부 반복·결과 회수까지 포함합니다. C 내부 max_ns와 Python max_ms는 측정 대상부터 다르므로 둘이 일치할 이유가 없습니다. 이 파일은 워밍업1회를 제외한5회 값을 모아 중앙값과 최대값을 출력합니다. 단위를 맞춘 뒤 비교하더라도 차이를 전부 함수 호출 오버헤드라고 설명할 수는 없습니다.

호스트 최대값은 이번 유한 관찰에서 가장 큰 값입니다. 다른 부하나 보드에서도 더 큰 지연이 없다는 최악 실행 시간 증명이 아닙니다. 짧은 실행을 여러 번 재는 이유는 운영체제 스케줄링과 준비 상태의 변동을 드러내기 위해서입니다. 작업 크기·반복수·빌드 옵션·환경·동시 실행 프로그램을 보고서에 적습니다. 숫자가 바뀌었다는 사실과 정확성 테스트 실패를 구분합니다. 호스트 지연을 테스트의 고정 정답으로 만들지 않습니다.

오류를 메시지와 관찰 범위로 좁힙니다

컴파일에서 undeclared identifier CLOCK_MONOTONIC가 나오면 소스 첫 줄의 POSIX 기능 매크로와 time.h 포함, 지원 환경을 확인합니다. 매크로는 시스템 헤더보다 먼저 있어야 합니다. 링크 오류와 C 문법 오류를 섞지 않습니다. 실습 Makefile에는 -std=c11 -Wall -Wextra -Werror가 있어 사용하지 않은 변수도 실패할 수 있습니다. 측정 코드를 임시로 지우며 경고를 숨기기보다 변수의 역할을 확인합니다.

FAIL 뒤에 표시되는 조건은 기대한 불변식입니다. missed가1이어야 하는데0이면 budget 비교의 초과 경계를 봅니다. 래핑 검사만 실패하면 signed 변환이나 차이의 자료형을 봅니다. 실행 전 빌드가 실패하면 아직 테스트 동작을 확인한 것이 아닙니다. starter의 실패는 컴파일 성공 뒤 발생해야 하며 solution과 같은 테스트를 사용합니다. 문제를 고친 뒤 통과 출력만 보지 말고 이전에 실패한 조건이 실제로 실행됐는지 확인합니다.

통계에는 손실도 필요합니다. 발행한 샘플만 재면 DROP_NEW로 버린 사건의 지연은 분포에서 빠집니다. 앞 모듈의 dropped·stale·cancelled 카운터는 계속 보존하고 지연 표 옆에 적습니다. 큐가 가득 찼던 실험에서 성공 샘플의 평균이 작다고 처리 능력이 충분하다고 결론내리면 안 됩니다. 이번 작은 실습은 유효 사건 집계 함수를 익히며, 누적 미션에서 큐와 연결합니다.

실험 기록은 명령·입력 사건·측정 구간·반복수·단위·최대값·누락 수의 순서로 남깁니다. 예를 들어 가상 due100/done121/budget20과 호스트 루프100회는 별도 행입니다. 수집과 전송 사이의 구간을 더 나누고 싶다면 같은 사건 ID를 유지해 시작과 끝을 연결합니다. 로그를 많이 출력한 버전은 계측이 동작을 바꾸는 실험으로 따로 표시합니다. 핀 관찰과 하드웨어 디버깅은 더 읽기의 장으로 이어갑니다.

참조한 Python 시간 API 문서는 perf_counter가 짧은 경과 시간 측정용이며 대기 시간도 포함한다고 설명합니다. POSIX 시계 문서에서 C 시계 계약을 확인할 수 있습니다. 문서의 기능 설명과 이 실습의 측정 범위를 구분하여 읽습니다. 어떤 API를 선택했다는 사실만으로 관심 사건의 양 끝이 자동으로 정해지지는 않습니다.

따라하기

starter의 누락 집계 확인

starter 폴더에서 실행합니다. 컴파일은 성공하고 초과 집계 조건 하나가 실패합니다.

make -s test

실행 결과

latency: 8 checks, 1 failures

구간 집계 완성

latency.c에서 유효성 검사 뒤 지연이 budget을 초과할 때 missed를 증가시킵니다. 수정 후 아래 명령의 실패 수가0인지 확인합니다. solution에서 직접 실행한 출력입니다.

make -s test

실행 결과

latency: 8 checks, 0 failures

두 시계의 측정 범위 확인

완성 코드에서 실행합니다. 아래는 이 맥의 실측 예이며 시간 수치는 반복 실행마다 달라집니다. C 구간과 자식 프로세스 경과 시간을 별도 행으로 기록합니다.

make -s measure

실행 결과

host repeats=100 warmup=1 max_ns=20000 mean_ns=10170 sink=754527704
process repeats=5 warmup=1 median_ms=5.059 max_ms=7.251
host=Darwin arm64 python=3.13.0

확인 문제

실습

latency_add를 완성합니다. NULL·역순 입력에서 통계를 보존하고 래핑5ms·등호20ms·초과21ms를 처리합니다. make test 뒤 make -s measure로 워밍업/반복수·단위·호스트 조건과 최대 시간을 README에 기록합니다. 호스트 숫자는 고정 정답이 아닙니다.

시작 코드·테스트 내려받기

실행 명령

make test

기대 결과

latency: 8 checks, 0 failures

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 호스트 최대 지연을 MCU 최악 실행 시간과 구분하는 근거를 설명해 주세요.
  • 응답 측정에서 시작 사건과 종료 사건은 어떻게 정하나요?