Skip to content

상시 결함 사냥: 근거 없는 상수, 조용한 성능 저하, 주석-코드 불일치, 낭비 재계산 #2006

Description

@seonghobae

한 번의 감사가 아니라 상시 작업으로 둔다. 이번 세션의 짧은 조사만으로 아래 결함들이 나왔고, 전부 같은 성격이다. 실행이 조용히 나빠지는데 아무도 알 수 없는 상태.

이번에 실제로 나온 사례

  1. 근거 없는 하드코딩 상수 + 조용한 성능 저하. analysis/library_bootstrap.py의 장치 프로브가 60초 하드코딩 타임아웃으로 서브프로세스를 띄운다. 스왑 포화 상태에서 프로브가 60초를 못 지키자 스크립트가 조용히 CPU로 폴백했다. 로그에는 TimeoutExpired after 60s만 남아 라이브러리나 Metal이 고장 난 것처럼 보였다. 실제로는 GPU 경로가 멀쩡했다. 실패를 숨기고 성능을 떨어뜨린 채 진행하는 구조다.

  2. 주석이 코드와 반대로 말한다. bifactor_grm.rs:565 부근의 GPU 분기 주석이 "Small host-side staging wrappers (no table data is duplicated beyond this Vec-of-Vec shell)"라고 적혀 있으나, 바로 아래에서 tables.to_vec(), tg.to_vec(), vec![vec![ts.to_vec(); v.n_specific]]를 EM 반복마다 실행한다. 실제 복사다.

  3. 반복마다 불변 데이터를 다시 만든다. 같은 지점에서 ReducedEstepInputs를 EM 반복마다 새로 구성하는데, y, observed, item_block, blocks, tg, ts, log_wg, log_ws는 반복 간에도 복제 간에도 변하지 않는다. 반복마다 바뀌는 것은 tables뿐이다.

  4. 병렬성 부재가 문서화되지 않았다. crates/fast-mlsirm-py/src/ 전체에 allow_threads 0회, crates/mlsirm-core/src/에 rayon 없음, par_iter 0회. 호출자가 스레드 풀로 n_jobs=5를 주면 완전히 직렬화되는데 그 사실이 어디에도 적혀 있지 않다. 사용자는 5배 빨라졌다고 믿는다. (병렬화 축 1: 복제 간 병렬 — PyO3 allow_threads + 분산 실행 백엔드 #2001, 병렬화 축 2: Rust 코어 내부 병렬 — E-step 인물 축 (결정적 축약) #2002)

  5. 중복 계산. E-step이 인물마다 블록 누적을 다시 하는데, 값은 블록의 응답 부분패턴에만 의존한다. 실제 자료에서 1020명이 71/64/103개 부분패턴으로 줄어든다. (병렬화 축 0 (최우선): 블록 단위 응답 부분패턴 축약 — E-step 지배항 약 13배 감소 #2003)

  6. 설명되지 않는 고정비. q=241, 1020명에서 max_iter를 1·2·4·8로 재면 CPU는 반복당 19.52초 + 고정 19.57초다. 고정비가 반복 한 번 값과 맞먹는다. 400복제면 EM 시작 전에만 2.2시간이 나간다. 무엇인지 아직 모른다.

찾을 결함 유형

워커는 아래 유형으로 저장소를 훑고 발견마다 이슈를 연다.

  • A. 근거 없는 상수. 타임아웃, 재시도 횟수, 배치 크기, 임계값, 버퍼 크기. 출처가 문헌·측정·명시적 정책 중 어느 것도 아닌 값. ADR-0028 대상.
  • B. 조용한 성능 저하. 실패를 삼키고 느린 경로로 넘어가는 분기. 폴백은 있어도 되지만 반드시 드러나야 한다. 어느 경로가 실제로 돌았는지 반환값이나 provenance에 남는가.
  • C. 주석·문서와 코드의 불일치. 특히 성능·복사·불변성을 주장하는 주석.
  • D. 반복 안의 불변 작업. 루프 불변 계산, 반복마다 재할당, 반복마다 재업로드. (#2004에 50개 중첩 목록)
  • E. 중복 계산. 같은 입력에 같은 결과를 반복 계산하는 지점.
  • F. 문서화되지 않은 성능 계약. 스레드 안전성, GIL 동작, 병렬 확장성, 결정성. 호출자가 알아야 하는데 적혀 있지 않은 것.
  • G. 설명되지 않는 비용. 프로파일에서 큰 몫을 차지하는데 무엇인지 아무도 모르는 구간.

방법

  • 측정이 먼저다. 유형 D·E·G는 프로파일 없이 손대지 않는다. 뜨겁지 않은 곳을 고치는 데 시간을 쓰지 않는다.
  • 발견마다 이슈 하나. 재현 방법, 측정값, 영향 범위를 함께 적는다. 뭉뚱그린 이슈를 만들지 않는다.
  • 수정마다 따로 측정. 여러 개를 한 PR에 넣고 합쳐서 재면 회귀 원인을 못 찾는다.
  • 재발 방지를 함께 낸다. 가능하면 CI 검사로, 불가능하면 이유와 대안을 ADR로.
  • 추정을 사실로 쓰지 않는다. 확인하지 못한 것은 "확인하지 못함"으로 적고 무엇을 시도했는지 남긴다.

우선순위

이 작업은 #2001#2003#2004#2002 뒤에서 상시로 돈다. 위 네 건이 이미 유형 D·E·F의 구체 사례이므로, 그것들을 처리하면서 같은 눈으로 나머지를 훑는다.

첫 과제 두 개를 지정한다.

  1. 유형 B 전수: analysis/crates/ 전체에서 실패를 삼키고 다른 경로로 넘어가는 분기를 찾아 목록화한다. 장치 프로브가 하나의 사례일 뿐인지 확인한다.
  2. 유형 G: 위 6번의 고정 19.57초가 무엇인지 프로파일로 규명한다.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions