Skip to content

perf(ioctl): cached-head fast path for ioctl_send_cmds#19

Closed
hyunyul-XCENA wants to merge 1 commit into
mainfrom
hycho/perf/send-cmds-cached-head
Closed

perf(ioctl): cached-head fast path for ioctl_send_cmds#19
hyunyul-XCENA wants to merge 1 commit into
mainfrom
hycho/perf/send-cmds-cached-head

Conversation

@hyunyul-XCENA
Copy link
Copy Markdown

@hyunyul-XCENA hyunyul-XCENA commented Apr 28, 2026

🤔 배경 및 동기 (Why)

ioctl_send_cmds는 모든 호출에서 device-side head 레지스터를 동기 PCIe read (read_ctrl_from_device, IO_OPCODE_SQ_READ)로 가져와 pushable_count를 산출한다. 큐가 saturate 되지 않은 일반 워크로드에서도 매번 PCIe 왕복 비용이 들어가서 호출당 ~30 μs.

같은 SQ를 다루는 ioctl_send_cmd_with_data는 cached sq_mbox->ctx.head 기반으로 진행하다가 is_full()이 true 일 때만 fallback read 하는 낙관적 패턴을 이미 사용 — 호출당 ~12 μs. 두 sibling 사이에 ~18 μs 격차가 구조적으로 존재.

PXL 레벨에서 측정한 영향 (24-sub 디바이스, 1 packet/sub, 1000 reps/exec):

  • SEND_CMDS(batch) mean: ~30 μs → ~10 μs (-67 %, light scenarios)
  • SEND_CMDS(1pkt) admin path mean: ~30 μs → ~9 μs
  • PXL Map::execute wall: 1pkt/sub clean 2.98 ms → 2.68 ms (-9.9 %), 2pkt/sub change 3.62 ms → 3.19 ms (-11.8 %), heavy/same 8.13 ms → 6.67 ms (-17.9 %)
  • Heavy + saturated 시나리오는 cached count 부족 → fall-through PCIe read → 호출당 비용 unchanged (~37 μs). 의도된 동작.

기능 / ABI / device-file interface / HW command layout 변경 없음. nr_cmds 반환 의미 동일.

🏗️ 설계 변경점

  • ioctl_send_cmds 의 lock 진입 직후 cached-head fast path 추가
    • cached pushable_count 가 caller 의 nr_cmds 를 만족하면 PCIe read_ctrl_from_device 생략
    • 부족할 때만 기존 동기 read 경로로 fall through

장단점:

  • 이득: 일반 워크로드에서 PCIe 왕복 1회 절약 → 호출당 ~18 μs (×24 sub × 호출 수)
  • 손실 케이스: device 가 cached head 이후 추가로 head 를 진전시켰을 때 실제 가용 슬롯보다 적게 push 할 수 있음 (cached_pushable ≤ real_pushable 항상 성립). caller 가 다음 호출에서 잔여 push 재시도. correctness 영향 없음 (overflow 가능성 없음), throughput 의 transient under-utilization 만 발생, self-correct.

📝 상세 구현 내용

ioctl.c, +23 / -5, 1 hunk:

mutex_lock(&sq_mbox->lock);

count = get_pushable_count(sq_mbox);
if (count < send_cmd.nr_cmds) {
    if (read_ctrl_from_device(...) <= 0) { ... return -EINTR; }
    sq_mbox->ctx.head = ctx.head;
    count = get_pushable_count(sq_mbox);
}

if (count == 0) goto out;
...

핵심 invariant: cached head 는 항상 real device head 보다 뒤쳐진다 (device 가 head 를 monotonic 하게 전진시키므로). 따라서 cached_pushable ≤ real_pushable. cached 가 충분하다고 보면 실제도 충분 — read 생략 안전.

리뷰어가 봐줬으면 하는 부분:

  • cached head invariant 의 holdup. 다른 path에서 sq_mbox->ctx.head 를 strictly stale 이외 방향으로 갱신하는 곳이 있는지 — 이게 깨지면 가정 무너짐.
  • Loss case 에서의 caller 재시도 흐름이 receiver/queue 관리 측에서 문제 일으킬 가능성 (현재까지 검토론 무관).

✅ 테스트

  • 단위 테스트
  • 통합 테스트
  • 수동 테스트 — xcena-gnr-94 실장에서 pxl-echo-bench 6 시나리오 (one/two-per-sub × same/change, heavy × same/change) 측정.
    • light (1pkt/2pkt-per-sub): SEND_CMDS_b mean ~30 μs → ~10 μs, wall -9 ~ -12 %
    • heavy: SEND_CMDS_b unchanged (~37 μs), wall 변화는 DI/DF + Wait 감소에 기인
    • 호출 회귀 (-EINTR, send count 누락 등) 관측 안 됨

상세 측정 데이터: PXL repo 의 phase5/.local/driver-cached-head-comparison.md (내부 share).

🔗 관련 이슈(선택)

🌿 관련 PR(선택)

  • PXL: Phase 5 (hycho/feat/map-prime-task-persist) 의 1pkt/sub clean-path 가 본 driver fix 후 baseline 대비 명확한 win 으로 전환됨. 독립적이지만 sibling improvement.

🌿 관련 Branch(선택)

📦 Release Note (자동 생성용 / 영문 작성)

NEW

CHANGED

  • driver: Reduced per-call overhead of command submission ioctl in light workloads by avoiding unnecessary synchronous PCIe reads.

FIXED

IMPORTANT NOTES

ioctl_send_cmds unconditionally fired a synchronous PCIe read of the
device-side head register (read_ctrl_from_device) before every batch
push. On a 24-sub PXL device this added ~18us per call, putting
SEND_CMDS at ~30us vs the ~12us achieved by the sibling
ioctl_send_cmd_with_data, which already uses a cached + busy-poll-on-full
pattern.

The cached head only ever lags the real device head (the device
monotonically advances head as it consumes commands), so cached_pushable
is always <= real_pushable. If the cached count already covers the
caller's nr_cmds, the actual count is at least as large -- safe to skip
the read. Only fall back to the synchronous read when the cached count
is short.

Worst-case behaviour: when the real head moved further than cached
between calls, we may push fewer commands than physically possible.
The caller resubmits the remainder on the next call. No correctness
impact (we never overflow the queue), only a transient throughput
underutilization that self-corrects.

Measured impact (PXL echo bench, one-per-sub, batch=1, 24 subs, 1000
reps): closes the ~600us per-execute gap between PXL's clean dispatch
path (which fires 24 SEND_CMDS calls) and the dirty path (which fires
24 SEND_CMD_WITH_DATA calls), eliminating the structural penalty for
1pkt/sub workloads in PXL Phase 5.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant