AEGLOSLAB ROS 2 Jazzy 대전

제11부 도구·관측성

11.7 성능 측정 — 지연·지터·처리량

11.7.1 이 장에서 배우는 것 / 선수 지식

이 책은 3.6.1에서 방침을 세웠다 — 성능 수치를 인용하지 않고 측정 방법을 가르친다. 이 장이 그 약속을 지키는 자리다.

측정이 중요한 이유는 두 가지다. 첫째, 성능 문제는 추측으로 진단되지 않는다. "느린 것 같다"에서 출발한 최적화는 대개 잘못된 곳을 고친다. 둘째, 성능은 회귀한다. 측정 체계가 없으면 어느 변경이 성능을 떨어뜨렸는지 나중에 알 수 없다.

배우는 것:

  • 무엇을 측정하는가 — 지표의 정의.
  • 종단 지연 측정 — 가장 중요한 지표.
  • 분포로 보기 — 평균이 거짓말하는 이유.
  • 처리량과 대역 — 자원 관점.
  • 자원 사용 — CPU·메모리.
  • 측정 방법론 — 워밍업·부하·반복.
  • 흔한 실수 — 잘못된 측정.
  • 회귀 체계 — CI에 넣기.
  • 내장 토픽 통계 — 코드를 안 고치고 구독별 주기·나이를 얻는 법과 그 관측 공백(11.7.12).
  • 계측 도구 다섯(11.7.13) — 이 책의 실측을 자기 노드에서 재현하는 법.

선수 지식: 2.5.6(주기·지터), 11.6(트레이싱), 6.9(진단), 4.10(CI), 7.8(대역).

정직: 이 장은 방법론이며 절대 수치를 제시하지 않는다 — 하드웨어·구성·부하에 따라 자릿수가 달라지기 때문이다(BK-03·3.6.1). 도구 이름·옵션은 버전 의존이다(확인 필요). 이 환경에서 검증하지 않았다(목차 3). 예외: 11.7.12(내장 토픽 통계)는 2026-08-16에 Jazzy 컨테이너에서 실행 확인했다.

11.7.2 무엇을 측정하는가

성능 지표 지연 latency 지터 jitter 처리량 throughput 자원 CPU · 메모리 · 대역 안정성 장시간 거동
지표정의왜 중요
종단 지연센서 획득 → 명령 출력제어 품질·안전을 좌우
구간 지연각 단계의 소요병목 특정
주기 지터실제 주기의 변동제어 안정성(2.5.6)
콜백 지속개별 처리 시간실행기 영향(2.2.7)
처리량초당 처리 건수용량
대역바이트/초네트워크·디스크(7.8)
CPU·메모리자원 점유여유·안정성
장시간 거동시간에 따른 변화누수·열화 발견

견해: 종단 지연이 로봇에서 가장 중요한 지표다. 센서가 무언가를 본 순간부터 로봇이 반응하기까지의 시간이 곧 반응성이고, 이것이 안전 여유(8.7.4)와 제어 성능을 결정한다. 단계별 최적화보다 종단 지연을 먼저 측정하는 것이 옳다.

목표를 먼저 정한다:

항목예
지표장애물 감지 → 감속 명령
목표값예: 100 ms 이내
백분위99% 이상이 목표 이내
조건정상 부하·목표 하드웨어

견해: 목표 없는 측정은 방향이 없다. "얼마면 충분한가"를 안전 요구·제어 요구에서 역산해 정하고(8.7.4의 정지 거리 계산이 그 예), 그 기준으로 측정 결과를 판정한다.

11.7.3 분포로 보기

평균은 로봇 성능 평가에서 거의 쓸모없다.

측정값 분포 평균: 시스템 전체 부하 감 중앙값: 전형적 경우 p95 · p99: 실질 최악 최댓값: 드문 사건 ★ 판정 기준
통계의미용도
평균총량자원 계획
중앙값전형참고
p95·p99실질 최악판정 기준
최댓값절대 최악안전·실시간
표준편차산포참고

견해: 로봇에서는 꼬리가 사고를 만든다(2.5.6·11.6.5의 반복). 평균 지연 20 ms에 최댓값 300 ms인 시스템은, 그 300 ms 순간에 사람 앞에서 못 멈춘다. "평균은 좋은데 가끔 이상해요"가 곧 문제 있음을 뜻한다.

견해: 분포를 히스토그램·시계열로 함께 보라(11.5.7). 히스토그램은 형태(단봉·이봉)를, 시계열은 패턴(주기적·산발적)을 알려준다. 이봉 분포는 두 가지 경로가 있다는 신호이고, 주기적 스파이크는 다른 주기 작업과의 간섭을 시사한다.

11.7.4 종단 지연 측정

가장 실용적인 방법은 header.stamp 전파다.

센서: stamp = t0 전처리 stamp 유지 인지 stamp 유지 계획 · 제어 최종 노드: now() - t0 = 종단 지연
// 최종 소비 지점에서
void control_callback(const my_msgs::msg::Target::SharedPtr msg)
{
  auto latency = (now() - msg->header.stamp).seconds();
  latency_stats_.add(latency);          // 통계 누적
  // 진단으로 발행 (6.9.5)
}

전제: 파이프라인 전체가 원본 스탬프를 유지해야 한다.

규칙이유
파생 메시지는 원본 stamp 계승지연 추적 가능
now()로 덮어쓰지 않는다7.7.4의 원칙
융합 시 대표 stamp 규칙일관되게

함정: 중간 노드가 header.stamp = now()로 덮어쓰면 종단 지연이 측정 불가능해진다. 7.7.4에서 "센서 스탬프를 보존하라"고 한 이유가 정합성만이 아니라 관측 가능성에도 있다.

함정: 분산 시스템이면 시계 동기가 전제다(7.7.6). 노드가 서로 다른 기기에 있고 시계가 안 맞으면, 측정된 "지연"에 시계 오차가 섞인다. PTP·NTP 상태를 먼저 확인한다.

구간 분해:

방법설명
각 단계에서 stamp와 현재 시각 비교누적 지연
차이로 구간 지연 계산병목 특정
트레이싱정밀(11.6.6)
// 각 노드가 자기 시점의 누적 지연을 진단으로 발행
diag.add("cumulative_latency_ms", (now() - msg->header.stamp).seconds() * 1000);

견해: "각 노드가 자기 시점의 누적 지연을 진단으로 내보내는" 패턴이 비용이 낮고 강력하다. 코드 몇 줄이면 되고, 운영 중에도 병목을 볼 수 있으며, 트레이싱 없이 구간 분해가 된다. 이 책이 반복해 온 "진단으로 내보내라"(6.9)의 대표적 응용이다.

11.7.5 주기와 지터

// 콜백 간격 측정
void timer_callback()
{
  auto t = now();
  if (last_.nanoseconds() > 0) {
    double interval = (t - last_).seconds();
    interval_stats_.add(interval);      // 분포 누적
  }
  last_ = t;
  // ... 실제 작업 ...
}
// 콜백 지속 시간
void callback(...)
{
  auto start = std::chrono::steady_clock::now();      // ★ steady_clock
  // ... 작업 ...
  auto dur = std::chrono::steady_clock::now() - start;
  duration_stats_.add(std::chrono::duration<double>(dur).count());
}

함정: 지속 시간 측정에는 steady_clock(단조 시계)을 쓰라. ROS 시계(now())는 시뮬 시간이면 점프하고, 시스템 시계는 NTP 보정으로 뒤로 갈 수 있다(1.10.5). 경과 시간에는 단조 시계가 원칙이다.

CLI 확인:

ros2 topic hz /scan            # 평균·최소·최대·표준편차
ros2 topic delay /scan         # stamp 기준 지연 — 헤더 없는 타입은 거부한다(실행 확인)
ros2 topic bw /camera/image_raw # 대역

함정: ros2 topic delay는 header가 있는 메시지에만 쓸 수 있다(실행 확인 — 없으면 msg does not have header로 거부한다). std_msgs/String처럼 헤더 없는 타입은 애초에 대상이 아니다. 이것이 1.2.7이 말한 "스탬프 없는 메시지는 나중에 시간을 못 따진다"의 구체적 대가다 — 진단 도구조차 쓸 수 없다.

함정: ros2 topic hz는 그 자체로 구독자를 하나 추가한다. 대용량 토픽이면 측정 행위가 시스템에 부담을 준다(관찰자 효과 — 11.6.7). 용량이 작은 토픽에는 편리하지만, 이미지·포인트클라우드에서는 주의한다.

견해: ros2 topic hz의 표준편차와 최소·최대를 보라. 평균만 보면 "10 Hz 정상"인데, 최솟값이 2 Hz면 가끔 크게 지연된 것이다. 11.7.3의 원칙이 CLI에서도 그대로다.

11.7.6 처리량·대역·자원

대역 계산(1.3.9·7.8.2·11.2.6에서 반복):

$$B = \sum_i S_{\text{msg},i} \cdot f_i \cdot n_{\text{sub},i}$$

$n_{\text{sub}}$가 곱해지는 것이 핵심이다 — 구독자가 늘면 대역도 는다(3.3.5의 멀티캐스트 예외 제외).

# 네트워크 실측
iftop -i wlan0
nload
sar -n DEV 1

# 프로세스별
nethogs

자원 측정:

# 노드별 CPU·메모리
top -H -p $(pgrep -d, -f my_node)       # 스레드별
ps -o pid,%cpu,%mem,rss,comm -p PID

# 시간에 따른 추이 (누수 탐지)
while true; do
  ps -o rss= -p PID
  sleep 60
done
항목확인
CPU %코어 대비 여유
RSS 추이증가하면 누수 의심
스레드 수예상과 일치(2.2.6)
파일 디스크립터누수
디스크 I/O기록 부하(11.2.6)

견해: 메모리 추이를 장시간 관찰하는 것이 누수 탐지의 실용적 방법이다. 몇 시간 실행하며 RSS를 기록하면 증가 추세가 보인다. 운영 로봇은 며칠~몇 주 연속 동작하므로, 시간당 수 MB의 누수도 결국 문제가 된다.

견해: CPU 여유를 충분히 두라. 100%에 가까우면 지터가 급증하고(6.6.6), 예외 상황에서 대응할 여력이 없다. 평상시 여유를 확보하는 것이 안정성의 실질 조건이다 — 구체적 목표치는 시스템 특성에 따라 정한다.

11.7.7 측정 방법론

측정이 신뢰할 만하려면 절차가 있어야 한다.

1. 목표 · 지표 정의 2. 환경 고정 하드웨어 · 설정 · 부하 3. 워밍업 초기 구간 제외 4. 충분한 시간 측정 5. 반복 여러 회 · 변동 확인 6. 분포 분석 p95 · p99 · max 7. 기록 · 재현 정보
단계유의
2 환경목표 하드웨어에서 — 개발 PC 아님
2 부하실제와 유사한 부하
3 워밍업초기 할당·캐시·디스커버리 제외
4 시간꼬리를 포착할 만큼 길게
5 반복회차 간 변동 확인
7 기록커밋·설정·하드웨어

함정: 개발 PC에서 측정한 값은 임베디드 로봇을 대변하지 않는다(10.6.2의 ⑤ 실행 환경 간극). CPU·메모리 대역·캐시가 자릿수로 다를 수 있다. 목표 하드웨어에서 측정하는 것이 원칙이다.

함정: 부하 없이 측정한 값은 낙관적이다. 실제로는 항법·인지·기록이 동시에 실행되며 자원을 경쟁한다. 전체 시스템을 기동한 상태에서 측정해야 실효 값이 나온다.

견해: 워밍업 구간을 반드시 제외하라. 시작 직후에는 디스커버리(3.3), 메모리 할당, 캐시 미스, JIT 성격의 초기화가 겹쳐 값이 크게 악화된다. 수 초~수십 초를 버리고 정상 상태를 측정한다.

견해: 측정 조건을 기록하지 않으면 나중에 비교할 수 없다. 커밋 해시·설정 파일·하드웨어·부하 조건을 결과와 함께 남기면, 몇 달 뒤 "그때보다 느려졌다"를 판정할 수 있다.

11.7.8 흔한 실수

실수왜 틀리나
평균만 본다꼬리가 문제(11.7.3)
개발 PC에서 측정목표 환경과 다름
부하 없이 측정낙관적
워밍업 포함비관적·잡음
짧게 측정드문 사건 놓침
한 번만 측정변동 미확인
측정이 대상을 바꿈관찰자 효과(11.6.7)
now()로 경과 시간시계 점프(1.10.5)
시계 미동기 상태 비교지연에 오차 섞임
조건 미기록재현·비교 불가
최적화 후 재측정 안 함효과 불명
병목 아닌 곳 최적화노력 낭비

견해: "병목 아닌 곳 최적화"가 가장 흔한 낭비다. 종단 지연을 먼저 구간 분해하면(11.7.4), 어디를 고쳐야 하는지가 드러난다. 측정 없이 최적화하지 말라는 오래된 격언이 로봇에서도 그대로다.

견해: 측정 자체가 부하를 만든다는 점을 늘 의식하라. ros2 topic hz·트레이싱·RViz2(11.4.5)·기록(11.2.6)이 모두 자원을 쓴다. 측정 도구를 끈 상태의 종단 지표와 비교해 영향을 확인한다.

11.7.9 회귀 체계

성능은 조용히 악화된다. 감시 체계가 필요하다.

아니오 예 성능 시험 스위트 CI에서 정기 실행 4.10.2 지표 기록 기준 초과? 경보 · 조사 이력 축적 추세 관찰
계층내용
단위 성능알고리즘 함수 시간
통합 성능시뮬·bag 기반 종단 지연
자원CPU·메모리
실기 정기 측정실제 하드웨어

함정: CI 러너의 성능은 변동한다(4.10.6). 공유 러너에서 측정한 절대 시간은 신뢰도가 낮다. 여유 있는 임계로 큰 회귀만 검출하거나, 전용 하드웨어에서 측정한다.

견해: "큰 회귀만 검출하는" 것이 현실적이다. 5% 변동을 검출하려 하면 오탐이 대량 발생해 아무도 안 본다. 2배 느려짐 같은 명백한 회귀를 확실히 검출하는 것이 실질 가치가 크다.

견해: 운영 중 지표를 진단으로 상시 수집하는 것이 최종 방어선이다(6.9.5). 실기의 실제 부하·환경에서의 지연·주기·자원을 기록하면, CI가 검출하지 못한 회귀와 환경 변화를 발견할 수 있다. 11.3.2의 상시 기록에 성능 진단을 포함시키면 사후 분석도 가능해진다.

진단으로 내보낼 성능 지표:

지표주기
종단 지연 p951 Hz
주요 콜백 주기 지터1 Hz
CPU·메모리1 Hz
큐 적체1 Hz
드롭 카운트1 Hz

11.7.12 내장 토픽 통계 — 코드를 안 고치고 측정하는 법

번호에 대하여: 이 절은 나중에 추가되었다. 기존 번호를 밀지 않는다는 규약(CLD-03)에 따라 번호는 끝에 붙이고, 읽는 흐름을 위해 함정 카탈로그 앞에 놓았다.

지금까지의 측정은 계측을 심는 방식이었다 — 스탬프를 기록하고, 로그를 남기고, 트레이싱을 켠다. rclcpp에는 그보다 앞서 시도해 볼 것이 하나 있다. 구독 하나하나에 대한 주기·나이 통계를 미들웨어 계층이 직접 계산해 토픽으로 내보내는 기능이다.

// ① 노드 전체에 켜기
auto node = std::make_shared<rclcpp::Node>(
  "perf_probe", rclcpp::NodeOptions().enable_topic_statistics(true));

// ② 구독 하나만 켜기 (노드 설정과 무관하게)
rclcpp::SubscriptionOptions so;
so.topic_stats_options.state = rclcpp::TopicStatisticsState::Enable;
so.topic_stats_options.publish_topic = "/statistics";      // 기본값
so.topic_stats_options.publish_period = std::chrono::seconds(1);  // 기본값
sub_ = create_subscription<SensorMsg>("scan", rclcpp::SensorDataQoS(), cb, so);
옵션기본값비고
NodeOptions::enable_topic_statisticsfalse노드의 모든 구독에 적용
topic_stats_options.stateNodeDefaultEnable·Disable로 구독별 재정의
topic_stats_options.publish_topic/statistics바꿀 수 있다
topic_stats_options.publish_period1초0보다 커야 한다
topic_stats_options.qosSystemDefaultsQoS().keep_last(10)구독 QoS와 분리된 안정적 QoS

실행 확인(2026-08-16): 위 표는 rclcpp/subscription_options.hpp·node_options.hpp 원문이며, 노드 옵션만 켜고 구독 옵션은 변경하지 않아도 /statistics가 나타나는 것을 실행으로 확인했다.

11.7.12.1 무엇이 나오는가

$ ros2 topic echo /statistics
measurement_source_name: probe          # 측정한 노드
metrics_source: message_period          # 또는 message_age
unit: ms
window_start: {sec: …}
window_stop:  {sec: …}
statistics:
- {data_type: 1, data: 33.4}            # 평균
- {data_type: 3, data: 51.2}            # 최대
- {data_type: 2, data: 29.8}            # 최소
- {data_type: 5, data: 30.0}            # 표본 수
- {data_type: 4, data: 4.1}             # 표준편차

메시지 타입은 statistics_msgs/msg/MetricsMessage이며, data_type은 statistics_msgs/msg/StatisticDataType의 상수다 — 실행 확인.

값상수의미
1STATISTICS_DATA_TYPE_AVERAGE평균
2STATISTICS_DATA_TYPE_MINIMUM최소
3STATISTICS_DATA_TYPE_MAXIMUM최대
4STATISTICS_DATA_TYPE_STDDEV표준편차
5STATISTICS_DATA_TYPE_SAMPLE_COUNT표본 수

두 지표의 뜻이 다르다 — 이 구분이 이 기능의 값어치다.

metrics_source측정하는 것답해 주는 질문
message_period이 구독이 메시지를 받은 간격"센서가 약속한 주기로 오고 있나"(11.7.5)
message_age메시지 헤더의 스탬프와 수신 시각의 차이"데이터가 얼마나 묵었나" — 종단 지연의 부분집합(11.7.4)

11.7.12.2 한계 — 이것으로 안 되는 것

함정: message_age는 헤더가 있는 메시지에만 의미가 있다. std_msgs/msg/String처럼 std_msgs/msg/Header가 없는 타입으로 실행하면 message_age의 모든 통계가 NaN으로 나온다(실행 확인). 표본 수가 0인 창(window)에서도 마찬가지다. /statistics에 NaN이 가득하다면 고장이 아니라 스탬프가 없다는 뜻이다(7.1.6).

그리고 이 기능이 측정하는 것은 구독 지점까지다.

재 준다못 측정한다
수신 간격, 수신 시점의 메시지 나이콜백 실행 시각(executor 대기 시간이 빠진다 — 2.2)
구독별로 분리된 값콜백 처리 시간, 체인 전체의 종단 지연(12.4)
코드 수정 없이(노드 옵션만으로)발행 측 지표

견해: 성능 조사의 0단계로 쓰라. 노드 옵션 한 줄이면 켜지고, /statistics를 기록해 두면(11.3.2) "언제부터 20 Hz가 아니었나"를 사후에 답할 수 있다. 그런데 "콜백이 지연된다"는 문제는 이것으로 안 보인다 — 수신은 제때 됐는데 executor가 늦게 부르는 경우가 정확히 그 관측 공백다. 거기서부터는 트레이싱(11.6)이 필요하다. 순서는 /statistics → 트레이싱 → 손계측이 경제적이다.

함정: 켜면 비용이 든다. 구독마다 계산과 1초 주기 발행이 추가된다. 고빈도 토픽이 수십 개인 노드에서 전부 켜면 그 자체가 부하이며, 특히 무선으로 /statistics가 나가면 대역을 소모한다(7.8.6). 조사할 때 켜고, 상시 수집은 대상을 골라서 하라. 비용의 크기는 환경 의존이므로 직접 측정해야 한다(확인 필요).

정직: rclpy에서 같은 기능이 제공되는지는 확인하지 않았다(확인 필요). 위 서술과 실측은 모두 rclcpp 기준이다.

11.7.13 이 책의 실측을 자기 노드에서 재현하는 법 — 계측 도구 다섯

이 책의 여러 장에 붙은 실측 절은 전부 아래 다섯 가지 도구로 얻은 것이다. 어느 것도 특별한 패키지가 필요 없고, 대부분 스무 줄 안쪽이다. 독자가 자기 노드에서 같은 숫자를 얻을 수 있도록 한자리에 모은다.

각 도구에 "쓰면서 실제로 걸린 함정"을 함께 적는다 — 그것을 모르면 틀린 숫자를 얻고도 맞다고 믿게 된다.

11.7.13.1 할당 후크 — "이 경로에 할당이 있는가"

static std::atomic<bool>   counting{false};
static std::atomic<size_t> n_new{0};

void * operator new(size_t sz) {
  if (counting.load(std::memory_order_relaxed))
    n_new.fetch_add(1, std::memory_order_relaxed);
  void * p = std::malloc(sz);
  if (!p) throw std::bad_alloc();
  return p;
}
void operator delete(void * p) noexcept { std::free(p); }
void operator delete(void * p, size_t) noexcept { std::free(p); }

구간을 counting = true/false로 감싸고 반복 횟수로 나누면 회당 할당 수가 나온다.

답하는 질문이 책의 결과
정상 운전 구간이 무할당인가아니다 — 지속 executor로도 회당 7~9회(12.3.12.2)
어떤 API가 비용이 큰가rclcpp::spin_some(node) 자유 함수는 회당 143회(12.3.12.1)
발행 방식의 차이const& 재사용 0회 vs unique_ptr 1회(12.3.12.3)

함정: 초기화 구간을 빼고 재라. 노드·발행자·구독자 생성만으로 13,088회가 나온다(12.3.12.4). 디스커버리가 끝난 뒤 예열을 거쳐 측정하지 않으면 그 값에 묻힌다.

11.7.13.2 겹침 계측 — "콜백이 정말 동시에 실행되는가"

static std::atomic<int> inflight{0}, max_inflight{0};

void on_callback() {
  int cur = inflight.fetch_add(1) + 1;
  int mx = max_inflight.load();
  while (cur > mx && !max_inflight.compare_exchange_weak(mx, cur)) {}
  /* … 실제 작업 … */
  inflight.fetch_sub(1);
}
답하는 질문이 책의 결과
콜백 그룹 설정이 의도대로인가동시성 매트릭스 7행 전수 확인(2.4.12.1)
Reentrant가 실제로 동시 실행하는가최대 4(스레드 수만큼)(2.4.12.2)

함정 — 이 책이 두 번 걸렸다: 동시성 실험은 "겹쳤는지"를 먼저 계측해야 한다. 콜백이 주기보다 짧으면 Reentrant 그룹이어도 겹치지 않는다. 그 상태로 경쟁 조건을 측정하면 "안전하다"는 잘못된 결론이 나온다(2.9.10.4에서 실제로 그렇게 될 뻔했다). max_inflight가 2 이상인지 확인한 뒤에 결과를 읽으라.

11.7.13.3 주기 분포 — "드리프트인가 지터인가"

콜백 진입 시각을 벡터에 모으고 차분의 분위수와 누적 오차를 낸다.

std::vector<double> t;                       // 콜백마다 진입 시각(ms)
// 종료 후
std::vector<double> d;                       // 차분
for (size_t i = 1; i < t.size(); ++i) d.push_back(t[i] - t[i-1]);
std::sort(d.begin(), d.end());
double 누적오차 = (t.back() - t.front()) - T_ms * (t.size() - 1);

두 값을 함께 보는 것이 핵심이다 — 11.7.3의 분위수는 지터를, 누적 오차는 드리프트를 가른다.

답하는 질문이 책의 결과
드리프트가 있는가1500 주기에 0.000%(2.5.13.2)
executor 선택이 꼬리에 영향을 주는가Static +0.125 ms < Single +0.528 < Multi +1.161(2.5.13.3)

함정: -O2로 빌드하라. 디버그 빌드의 지터는 측정 대상이 아니라 빌드의 성질이다.

11.7.13.4 링크 확인 — "이 층이 정말 저 층만 부르는가"

readelf -d <라이브러리 또는 실행파일> | grep NEEDED     # 직접 의존
nm -D --undefined-only <라이브러리> | grep <심볼>       # 런타임 결합 여부
strings <라이브러리> | grep -E "^rmw_"                  # 하드코딩된 이름

코드를 읽지 않고 구조를 확정하는 가장 비용이 낮은 방법이다.

답하는 질문이 책의 결과
사용자 코드가 벤더를 링크하는가0회 — 재컴파일 없이 RMW를 바꿀 수 있는 근거(2.1.12.1)
계층도에 빠진 층이 있는가rmw_implementation이 있었다(2.1.12.3)

11.7.13.5 유실 주입 — "이 링크에서 견디는가"

tc qdisc add dev eth0 root netem loss 5%      # NET_ADMIN 권한 필요
tc qdisc del dev eth0 root                    # 원복
답하는 질문이 책의 결과
RELIABLE의 대가는 무엇인가처리량이 아니라 꼬리 지연(p99가 100~200배)(3.9.13.1)
메시지 크기가 얼마나 중요한가유실 5%에서 1 KB 92% ↔ 256 KB 0%(3.9.13.3)

함정: 유실이 있는 링크에서는 측정 자체가 불안정하다. 디스커버리도 같은 링크를 타므로 준비 시간이 불규칙하다. 한 번의 결과로 결론 내지 말고 반복해 범위로 보고하라(3.9.13.6).

11.7.13.6 공통 원칙 — 측정을 먼저 반증하라

다섯 도구 모두에서 같은 실수를 반복해 겪었다.

겪은 일방어
intra-process를 꺼도 버퍼 주소가 일치했다(할당자 재사용)대조군을 함께 실행한다(2.7.13.5)
Reentrant인데 유실이 0이었다(겹치지 않았다)전제를 계측한다(11.7.13.2)
유실 링크에서 RELIABLE 수신이 0이었다(재현 안 됨)반복하고 범위로 본다(3.9.13.6)
tracemalloc 스냅숏 차이가 앞뒤 안 맞는 값을 냈다도구가 무엇을 세는지 확인한다(아래)

tracemalloc의 스냅숏 차이는 "할당 횟수"가 아니라 "잔존 블록"이다. 루프 안에서 만들고 버리는 객체는 차이가 0에 가깝게 나오므로, 그것으로 할당량을 측정하면 "메시지 재사용이 새 객체보다 비용이 크다" 같은 앞뒤 안 맞는 값이 나온다(81차에 겪었다). Python에서 할당 "양"을 측정하려면 get_traced_memory()의 peak를, 비용을 측정하려면 시간을 보라.

놀라운 결과가 나오면 측정 장치를 먼저 의심하라. 이 책의 실측 회차에서 틀린 결론에 가장 가까이 갔던 순간은 전부 "측정이 옳다고 가정했을 때"였다.

11.7.10 함정 카탈로그

#함정증상대처
1평균으로 판정꼬리 놓침p95·p99·max
2개발 PC 측정실기와 다름목표 하드웨어
3무부하 측정낙관적전체 시스템
4워밍업 미제외비관적초기 버림
5now()로 경과 측정점프 오류steady_clock
6stamp 덮어쓰기종단 지연 측정 불가원본 유지(7.7.4)
7시계 미동기오차 섞임PTP·NTP 확인
8관찰자 효과 무시오판유무 비교
9조건 미기록비교 불가커밋·설정 기록
10측정 없이 최적화낭비구간 분해 먼저
11CI 절대 시간에 민감한 임계오탐큰 회귀만
12메모리 추이 미관찰장기 누수장시간 기록
13CPU 여유 없음지터 급증여유 확보
14/statistics의 NaN을 고장으로 오해무익한 작업헤더 없는 타입은 message_age 계산 불가(11.7.12.2)
15토픽 통계를 상시 전부 켬그 자체가 부하·대역조사 시 대상만(11.7.12.2)
16동시성 실험에서 겹침을 계측 안 함"안전하다"는 오결론max_inflight 확인(11.7.13.2)
17놀라운 결과를 그대로 채택측정 장치의 오류대조군을 함께 실행한다(11.7.13.6)

11.7.11 체크포인트

  • 성능 지표 여덟 가지와 각각의 중요성을 말할 수 있는가. 로봇에서 가장 중요한 것은?
  • 목표를 먼저 정하라는 조언에서, 목표에 포함되어야 할 네 요소는?
  • 평균이 쓸모없는 이유와, 판정 기준으로 써야 할 통계는?
  • 히스토그램과 시계열이 각각 알려주는 것은? 이봉 분포는 무엇을 시사하는가.
  • header.stamp 전파로 종단 지연을 측정하는 방법과, 그 전제 셋은?
  • stamp를 덮어쓰면 안 되는 이유 둘(정합성 외)은?
  • "각 노드가 누적 지연을 진단으로 내보내는" 패턴의 이점 셋은?
  • 경과 시간 측정에 steady_clock을 써야 하는 이유는?
  • ros2 topic hz의 주의점과, 무엇을 함께 봐야 하는가.
  • 대역 계산식에서 $n_{\text{sub}}$가 곱해지는 의미는?
  • 메모리 추이 관찰이 중요한 이유는?
  • 측정 방법론 일곱 단계와, 각 단계의 유의점을 말할 수 있는가.
  • 흔한 측정 실수 열두 가지 중 "가장 흔한 낭비"는 무엇인가.
  • 성능 회귀 체계의 네 계층과, CI에서 "큰 회귀만 검출하라"는 조언의 근거는?
  • 운영 중 진단으로 내보낼 성능 지표 다섯 가지를 들 수 있는가.
  • 코드를 고치지 않고 구독별 주기·나이를 얻는 방법은 무엇이며, 그 값이 콜백 실행 시각을 포함하지 않는다는 사실이 진단에 주는 한계는 무엇인가.
  • 회당 할당 수를 측정하려면 무엇을 후킹하며, 어느 구간을 빼야 하는가(11.7.13.1).
  • 동시성 실험에서 결과를 읽기 전에 반드시 확인할 값은 무엇인가(11.7.13.2).

다음 장 11.1은 제11부의 마지막 — 이 모든 도구를 현장에서 실제로 쓰는 순서로 구성한 디버깅 플레이북이다.