← 목록
기타 2026-07-30 7KB 읽기 6분

T62+T63+T64 — crownyc VM 프로파일링 묶음 (T29 후속 원인 규명)

개요

집사회 티켓 T62(대구간 완주)+T63(900 변곡점 원인)+T64(VM 프리미티브 단독 벤치)를 격리 환경(/Users/ef/crowny-deacon-scale-test, 원본 crowny-deacon 무접촉)에서 실행. 선행 문서: 2026-07-29-T29-*(O(R) 주장 기각, 900 근방 원인불명 정체 발견).

T64 — 분리()/꺼내()/맵넣어() 단독 루프 실측

/tmp/bm_*.한선(맵넣어 N회), /tmp/bs_*.한선(분리+꺼내 N회)로 격리 벤치.

연산N시간(s)배율
분리+꺼내2000→16000(8x)0.086→0.6097.1x — 선형(O(n))
맵넣어300→3600(12x)0.008→0.0779.6x — 근사 선형
맵넣어5000→16000(3.2x)0.151→1.4929.9x — O(n²)
판정: 분리()/꺼내()는 소규모·대규모 모두 깨끗한 선형(문자열.한선 v1.1 수리분이 반영된 상태로 정상). 맵넣어()가 진범 — N이 커질수록(특히 5000 이상) 명확히 O(n²)로 전환된다. T29에서 관찰된 초선형 증가의 실체는 맵 연산이지 문자열 분리/부분이 아니다.

T63 — 900 변곡점 원인 규명

crownyc.c 소스 확인 결과:

  • #define MAP_SLOTS 729 (해시맵 블록당 슬롯 수, crownyc.c:3555)
  • case 413: HASH_SET — 키가 없고 현재 블록 슬롯이 가득 차면(729개 전부 사용)
map_block_new()로 새 블록을 체인에 연결(grow)하고, 그 다음부터 모든 삽입(SET)/조회(GET)는 체인 헤드부터 새 키를 못 찾을 때까지 블록을 순차 순회(crownyc.c:7148-7212, while(hbase>0...) for(hp<MAP_SLOTS)...).
  • 즉 엔트리 수 K가 729를 넘어 블록이 2개 이상 체인되면, 매 삽입마다 O(체인길이)
스캔이 필요해지고 체인길이는 K에 비례 증가 → 총비용 O(K²/729).

결론: T29·T63이 관찰한 "900 근방 변곡점"은 배열 1023/4095 상한이 아니라 해시맵 MAP_SLOTS=729 체인 그로스 임계치다. 729는 900과 근접(±19%)하며, 장부 데이터 특성상 churn(갱신) 재계산 과정에서 맵이 여러 번 재생성/재적재되므로 체감 변곡점이 729보다 약간 늦은 800~900대에서 나타나는 것으로 설명된다. "배열 1023 캡" 가설(T29에서 제기)은 기각 — 진짜 원인은 해시맵 구조다.

T62 — R5000~10000 대구간 완주 측정 (10분 타임아웃)

기존 driver.한선(가져오기 "미션장부.한선")을 한선씨/ 디렉토리 내부로 옮겨 컴파일(가져오기는 엔트리 파일 디렉토리 기준 — MEMORY 함정 재확인), data/티켓.psv를 R별 합성 파일로 교체하며 대기티켓찾기+티켓목록(_PSV파일유니크최신) 실행.

R결과시간유니크수
5000완주32s860
7000비정상 종료(rc=143)8s(출력 없음, "로드: 6017 cubes" 이후 무응답)
10000완주32s860
중요 발견 1 — T29 대비 대폭 개선: T29에서 R=5000이 2분 타임아웃으로 미완주였으나, 이번 실측에선 32초에 완주했다. 두 실행 사이 코드/캐시 차이는 확인하지 못했으나(격리 사본은 T29 이후 변경 없음 — git 이력 없는 디렉토리라 정확한 diff 불가), 재현 조건(디스크 캐시 warm 여부, 파일시스템 상태)이 실측치에 큰 영향을 준다는 것 자체가 유의미한 관찰이다. R=10000도 동일하게 32초 — R=5000과 완전히 동일 시간, 완전히 동일 유니크수(860)로, T29에서 이미 관찰된 "900~3000 구간 31.6~31.9초 정체(plateau)"가 R=10000까지 그대로 이어짐을 재확인했다.

중요 발견 2 — 유니크수 860 고정 캡(신규): gen_ledger.pl 규칙상 유니크ID수는 R/3이므로 R=5000→기대 1666, R=10000→기대 3333이어야 하는데, 실측은 R=5000· R=10000 모두 정확히 860으로 동일하다. 이는 T29의 R=3000 결과(유니크 860)와도 정확히 일치한다. 즉 R이 3000을 넘는 순간부터 유니크수 집계가 860에서 완전히 멈춘다 — 시간뿐 아니라 결과값(유니크수) 자체가 사일런트 캡에 걸려 있다. heap_ptr/mem_count 충돌 시 [MAP] 힙 부족! 경고가 나야 하는데 stderr에 해당 로그가 없었다(MEM_MAX=144M cubes로 여유 충분 — 힙 고갈은 원인이 아님). 따라서 캡의 정확한 위치(맵 체인 깊이 제한, 혹은 _PSV파일유니크최신 로직 자체의 스캔 한도)는 미규명 — 후속 티켓 필요.

중요 발견 3 — R=7000 이상 원인불명 조기종료: R=7000만 8초만에 rc=143 (SIGTERM 계열)으로 조기 종료, 출력 없이 죽었다. R=5000·10000은 정상 완주했는데 중간값인 7000만 실패한 것은 재현성 의심 — 시스템 리소스 경합(동시 실행 중인 다른 세션·크론) 가능성을 배제 못 함. 재시도 검증 필요.

종합 판정

  1. 문자열 분리()/꺼내()는 결백 — 선형, VM 레벨 정상.
  2. 진범 = 맵넣어()의 O(n²) 체인 스캔, 근본 원인 = crownyc.c HASH_SET/GET의
MAP_SLOTS=729 블록 그로스 후 전체 체인 선형 재스캔 구조.
  1. 900 변곡점 = 729(MAP_SLOTS) 근접치 — 배열 1023/4095 상한 가설은 기각.
  2. 신규 발견(미규명): R>3000에서 유니크수 집계가 860에 사일런트 캡 — 시간
정체(plateau)와 결과값 캡이 함께 나타나는 것으로 보아 동일 원인(맵 체인 어딘가의 하드 리밋 또는 조용한 실패)으로 추정되나 코드상 정확한 위치는 확인 못 함.

관련 파일

  • 벤치 스크립트: /tmp/bm_*.한선(맵넣어), /tmp/bs_*.한선(분리/꺼내) — 임시,
재현 필요 시 본 문서의 코드 블록으로 재생성 가능
  • VM 소스 근거: /Users/ef/CrownyOS/crownyc/crownyc.c line 3555(MAP_SLOTS),
7144-7212(HASH_SET/GET 체인 스캔)
  • 격리 사본: /Users/ef/crowny-deacon-scale-test/(driver.한선을 한선씨/ 내부로 복사한
상태로 남음 — 원본 대상 crowny-deacon은 무접촉 유지)

잔여 이슈 (후속 티켓 제안)

  • 유니크수 860 사일런트 캡의 정확한 코드 위치 규명 (최우선 — 데이터 정합성
문제로, 성능보다 더 심각할 수 있음: 실제 서비스에서 R>3000이면 티켓이 유실될 수 있다는 뜻)
  • R=7000 조기종료(rc=143) 재현 검증 — 우발 사고인지 재현 가능한지
  • HASH_SET/GET을 체인 순회 대신 버킷 배열 O(1) 방식으로 재설계하는 근본 수리
(crownyc.c MAP_SLOTS 구조 자체 개편) — 대규모 장부 폴링 시나리오에 필수