존 카맥에게 연관된 '성능 우선' 마인드셋에 대한 실용 가이드: 프로파일링, 프레임 타임 예산, 트레이드오프, 복잡한 실시간 시스템의 출시 방법을 다룹니다.
왜 카맥의 접근법이 여전히 중요한가\n\n존 카맥은 종종 게임 엔진의 전설로 취급되지만, 유용한 부분은 신화가 아니라 재현 가능한 습관들입니다. 이는 한 사람의 스타일을 그대로 따라 하거나 “천재의 한 수”를 기대하는 것이 아닙니다. 마감과 복잡성이 쌓일 때 더 빠르고 부드러운 소프트웨어로 이어지는 실용적 원칙들에 관한 것입니다.\n\n### 성능 엔지니어링, 쉬운 말로\n\n성능 엔지니어링은 실제 하드웨어와 실제 조건에서 소프트웨어가 속도 목표를 만족하도록 만드는 것을 의미합니다—정확성을 깨뜨리지 않으면서요. 그것은 “무엇이든 빠르게 만들어라”가 아닙니다. 규율 있는 루프입니다:\n\n- 무엇이 "충분히 빠른가"를 정하고\n- 시간이 실제로 어디에 쓰이는지 측정하고\n- 한 번에 한 가지를 의도적으로 변경하고\n- 올바른 지표가 개선되었는지 검증한다\n\n이 마인드셋은 카맥의 작업 전반에 반복됩니다: 데이터로 논쟁하고, 변경을 설명 가능하게 유지하며, 유지보수 가능한 접근을 선호하세요.\n\n### 왜 실시간 그래픽스가 현실을 드러내는가\n\n실시간 그래픽스는 매 프레임마다 마감이 있기 때문에 용서가 없습니다. 마감을 놓치면 사용자는 즉시 버벅임, 입력 지연, 또는 불균일한 모션으로 느낍니다. 다른 소프트웨어는 큐, 로딩 화면, 백그라운드 작업으로 비효율을 숨길 수 있지만, 렌더러는 협상할 수 없습니다: 제때 끝내거나 못 끝내거나 둘 중 하나입니다.\n\n그래서 이 교훈은 게임을 넘어서 일반화됩니다. UI, 오디오, AR/VR, 트레이딩, 로보틱스처럼 지연이 빡빡한 시스템은 예산 단위로 사고하고 병목을 이해하며 돌발 스파이크를 피하는 사고방식에서 이득을 봅니다.\n\n### 얻을 수 있는 것\n\n체크리스트, 휴리스틱, 의사결정 패턴을 얻을 것입니다: 프레임 타임(또는 지연) 예산을 설정하는 법, 최적화 전에 프로파일링하는 법, 고칠 "한 가지"를 고르는 법, 회귀를 방지해 성능을 일상적으로 유지하는 법 등입니다.\n\n## 프레임 타임 예산으로 생각하라, 감으로 말하지 말라\n\n카맥 스타일 성능 사고는 간단한 전환에서 시작합니다: 주요 단위로 "FPS"를 말하는 것을 멈추고 프레임 타임으로 말하기 시작하세요.\n\nFPS는 역수이므로(“60 FPS”는 좋아 보이고 “55 FPS”는 비슷하게 들림) 사용자 경험은 각 프레임이 걸리는 시간—그리고 그 시간들의 일관성—에 의해 좌우됩니다. 16.6 ms에서 33.3 ms로 점프하면 평균 FPS가 괜찮아 보여도 즉시 눈에 띕니다.\n\n### 프레임 타임 vs FPS (왜 프레임 타임이 우위인가)\n\n- FPS는 변동성을 숨깁니다. 두 빌드가 모두 “평균 60 FPS”라도 한쪽은 가끔 40–60 ms 프레임 때문에 버벅일 수 있습니다.\n- 프레임 타임은 작업에 직접 매핑됩니다. 각 밀리초는 시스템에 귀속할 수 있는 실제 CPU/GPU 작업량입니다.\n- 목표가 더 명확합니다. “16.6 ms 이하를 유지하라”는 구체적 요구사항입니다; “부드럽게 느껴져라”는 아닙니다.\n\n### 예산: 당신이 실제로 쓰고 있는 것들\n\n실시간 제품은 단순히 “더 빨리 렌더”가 아니라 여러 예산을 가집니다:\n\n- CPU 시간(시뮬레이션, 게임 로직, 애니메이션, 컬링, 드로우 콜 제출)\n- GPU 시간(셰이딩, 포스트프로세싱, 오버드로우, 해상도)\n- 메모리(풋프린트, 스파이크, 단편화, 스트리밍 여유)\n- 로딩 시간(부팅, 레벨 로드, 셰이더 컴파일, 스트리밍 정지)\n\n이들 예산은 상호작용합니다. GPU 시간을 줄이기 위해 CPU 중심의 배칭을 도입하면 역효과가 날 수 있고, 메모리를 줄이면 스트리밍이나 압축 해제 비용이 늘어날 수 있습니다.\n\n### 예: 60 FPS에서 16.6 ms\n\n목표가 60 FPS라면 전체 예산은 프레임당 16.6 ms입니다. 대략적 분배는 이렇게 보일 수 있습니다:\n\n- CPU: 7 ms(시뮬레이션, 게임플레이, 가시성)\n- GPU: 9 ms(렌더 + 포스트)\n- OS/드라이버 + 오버헤드 버퍼: 약 0.6 ms\n\nCPU나 GPU 어느 하나라도 예산을 초과하면 프레임을 놓칩니다. 그래서 팀들이 “CPU 바운드”나 “GPU 바운드”라고 말하는데—그건 꼬리표가 아니라 다음 밀리초를 현실적으로 얻을 곳을 결정하는 방법입니다.\n\n### “충분히 빠름”은 제품 요구사항이다\n\n요점은 고사양 PC에서의 최고 FPS 같은 허영 지표를 쫓는 것이 아닙니다. 포인트는 당신의 대상 사용자에게 무엇이 충분히 빠른지—하드웨어 목표, 해상도, 배터리 제한, 발열, 입력 반응성—를 정의하고 성능을 관리하고 옹호할 수 있는 명시적 예산으로 취급하는 것입니다.\n\n## 먼저 프로파일링하라: 측정한 다음 결정하라\n\n카맥의 기본 움직임은 “최적화”가 아니라 “검증”입니다. 실시간 성능 문제는 그럴듯한 이야기들로 가득합니다—GC 일시정지, “느린 셰이더”, “드로우 콜 과다” 등—그리고 그 대부분은 당신의 빌드와 당신의 하드웨어에서는 틀립니다. 프로파일링은 직관을 증거로 대체하는 방법입니다.\n\n### 추측하기 전에 측정으로 시작하라\n\n프로파일링을 비상용 도구가 아니라 1급 기능처럼 취급하세요. 프레임 타임, CPU 및 GPU 타임라인, 그리고 이를 설명하는 카운트(삼각형, 드로우 콜, 상태 변경, 할당, 가능하다면 캐시 미스)를 캡처하세요. 목표는 한 가지 질문에 답하는 것입니다: 시간이 실제로 어디에 가고 있는가?\n\n유용한 모델: 느린 각 프레임마다 하나의 요소가 제한요소입니다. 아마도 무거운 패스에 묶인 GPU, 애니메이션 업데이트에 묶인 CPU, 또는 동기화로 정체된 메인 스레드일 수 있습니다. 그 제약을 먼저 찾으세요; 나머지는 노이즈입니다.\n\n### 과학자처럼 반복하라\n\n규율 있는 루프가 소모를 막아줍니다:\n\n- 반복 가능한 장면과 카메라 경로로 기준선을 측정\n- 한 가지를 변경\n- 재측정하고 델타를 기록\n\n개선이 분명하지 않으면(또는 기록되지 않았으면) 도움이 되지 않은 것으로 간주하세요—다음 콘텐츠 추가 시 살아남지 못할 가능성이 큽니다.\n\n### 위약(플라시보) 최적화에 주의하라\n\n성능 작업은 자기기만에 특히 취약합니다:\n\n- 벤치마크 실수: 일관성 없는 테스트 장면, 디버그 빌드, 백그라운드 작업, 발열로 인한 쓰로틀링, vsync 차이\n- 확증 편향: 프레임타임 데이터 없이 “더 빠르게 느껴진다”는 판단\n- 오해의 평균: 평균이 좋아져도 스파이크가 더 심해질 수 있음\n\n먼저 프로파일링하면 노력을 집중시키고, 결정들을 정당화하며, 리뷰에서 변경을 방어하기 쉬워집니다.\n\n## 병목: 실제로 느린 한 가지를 찾아라\n\n실시간 성능 문제는 모든 것이 동시에 일어나기 때문에 지저분하게 느껴집니다: 게임플레이, 렌더링, 스트리밍, 애니메이션, UI, 물리. 카맥의 본능은 소음을 뚫고 현재 프레임 시간을 정하는 지배적 제한요소—바로 지금 프레임 시간을 결정하는 한 가지—를 식별하는 것입니다.\n\n### 자주 맞닥뜨리는 병목 카테고리\n\n대부분의 느려짐은 몇 가지 버킷으로 나뉩니다:\n\n- CPU 바운드: 메인 스레드(또는 중요한 워커)가 제시간에 작업을 끝내지 못함—게임 로직, 드로우 콜 제출, 물리, 애니메이션 평가 등.\n- GPU 바운드: GPU가 프레임을 끝내지 못함—무거운 셰이더, 픽셀 과다, 비싼 포스트프로세싱, 복잡한 기하학.\n- 메모리 바운드: 대역폭/지연에 제한—캐시 미스, 부적절한 데이터 레이아웃, 많은 랜덤 접근, 큰 버퍼 복사.\n- I/O 바운드: 에셋 스트리밍, 셰이더 컴파일, 압축 해제, 파일 읽기, 네트워크 대기.\n\n이것을 레포트용 라벨로 붙이는 것이 목적이 아니라 적절한 레버를 고르는 것이 목적입니다.\n\n### (리라이트 전에) 빠르게 진단하는 방법\n\n몇 가지 빠른 실험으로 실제로 무엇이 제어하고 있는지 알 수 있습니다:\n\n- 해상도 스케일링 테스트: 렌더 해상도를 낮추거나 동적 해상도를 강제로 적용하세요. 프레임 타임이 크게 개선되면 GPU/픽셀 한계일 가능성이 큽니다. 거의 움직이지 않으면 CPU 또는 픽셀-비관여 GPU 작업을 찾아보세요.\n- 기능 토글: 그림자, SSR, AO, 파티클, 비용이 큰 패스들을 하나씩 끄세요. 의미 있는 변화는 시간의 흐름을 드러냅니다.\n- 계측과 캡처: 내장 타이머, CPU 프로파일러, GPU 캡처를 사용해 밀리초가 실제로 어디에 쌓이는지 보세요.\n\n### "한 큰 바위" 원칙\n\n10개 시스템에서 1%씩 줄이는 것으로는 거의 이기지 못합니다. 매 프레임 반복되는 가장 큰 비용을 찾아 먼저 공격하세요. 매 프레임마다 4 ms를 제거하는 단일 항목 제거는 수주간의 마이크로 최적화보다 낫습니다.\n\n### 병목은 이동한다\n\n큰 바위를 고친 후에 다음 큰 바위가 보이는 것은 정상입니다. 성능 작업을 루프로 취급하세요: 측정 → 변경 → 재측정 → 재우선순위화. 목표는 완벽한 프로파일이 아니라 예측 가능한 프레임 타임을 향한 꾸준한 진전입니다.\n\n## 부드러움이 이긴다: 스파이크, 버벅임, 꼬리 지연\n\n평균 프레임 타임이 괜찮아 보일 수 있지만 경험은 여전히 나쁠 수 있습니다. 실시간 그래픽스는 최악의 순간들으로 판단됩니다: 큰 폭발 중에 떨어진 프레임, 새로운 방에 들어갈 때의 히치, 메뉴가 열릴 때의 갑작스런 버벅임. 그것이 꼬리 지연입니다—드물지만 사용자에게 즉시 감지되는 느린 프레임들입니다.\n\n### 왜 꼬리가 평균보다 중요한가\n\n대부분의 시간에 16.6 ms로 동작하지만 몇 초마다 60–120 ms로 튈 경우, 평균이 여전히 20 ms로 찍혀도 “망가진” 느낌을 줍니다. 인간은 리듬에 민감합니다. 한 프레임의 긴 딜레이가 입력 예측성, 카메라 모션, 오디오/비주얼 동기화를 깨뜨립니다.\n\n### 스파이크의 일반적 원인\n\n스파이크는 종종 고르게 분배되지 않은 작업에서 옵니다:\n\n- 가비지 컬렉션이나 메모리 페이지 폴트로 인한 일시정지\n- "Just in time"으로 촉발된 셰이더 컴파일과 파이프라인 생성\n- 갑자기 압축 해제, 업로드, 파일 I/O가 필요한 에셋 스트리밍\n- OS 스케줄링과 백그라운드 작업이 CPU 시간을 빼앗거나(또는 주파수/열로 인한 변화)\n\n### 버벅임을 줄이는 전략\n\n비싼 작업을 예측 가능하게 만드는 것이 목표입니다:\n\n- 사전계산: 가능한 것은 미리 계산하세요—오프라인 셰이더 빌드, 데이터 베이킹, 룩업 테이블 준비.\n- 워밍업: 로딩 화면이나 제어된 워밍업 장면에서 셰이더와 파이프라인을 컴파일·생성하고 중요한 자산을 미리 터치하세요.\n- 암타이즈: 스트리밍, 압축 해제, 업로드를 여러 프레임으로 분산하세요.\n- 프레임당 작업 상한을 설정하세요(예: “이번 프레임 스트리밍 최대 2 ms”).\n\n### 꼬리를 기록하고 시각화하라\n\n평균 FPS 그래프만 그리지 마세요. 프레임당 타이밍을 기록하고 시각화하세요:\n\n- 히스토그램으로 프레임 타임의 군집과 이상치를 확인\n- 백분위수(p95, p99, p99.9)로 꼬리를 명시적으로 추적\n- 스파이크 마커와 연관 이벤트(GC 시작, 셰이더 컴파일, 에셋 로드) 표시\n\n최악의 1% 프레임을 설명할 수 없다면, 성능을 정말로 설명한 것이 아닙니다.\n\n## 트레이드오프를 명시하라(화질 vs 속도 vs 복잡성)\n\n성능 작업은 모든 것을 동시에 가질 수 있다는 척을 그만둘 때 훨씬 쉬워집니다. 카맥 스타일은 팀에게 거래를 소리 내어 이름 붙이게 합니다: 우리가 무엇을 얻고, 무엇을 치르는가, 그리고 누가 차이를 느끼는가?\n\n### 축(axes)을 이름 붙이고 실제 비용을 적어라\n\n대부분의 결정은 몇 가지 축 위에 있습니다:\n\n- 품질: 시각적 충실도, 시뮬레이션 정확성, 입력 감각\n- 속도: 프레임 타임, 로드 시간, 컴파일 시간, 반복 시간\n- 메모리: VRAM, RAM, 대역폭\n- 복잡성: 디버깅 난이도, 엣지 케이스, 테스트 부담 증가\n- 출시 시간: 일정 위험, 통합 위험, 팀 집중도\n\n변경이 한 축을 향상시키지만 조용히 세 축을 더 건드린다면 문서화하세요. “이것은 부드러운 그림자를 위해 GPU 0.4 ms와 VRAM 80 MB를 추가합니다”는 사용 가능한 진술입니다. “더 좋아 보인다”는 말은 그렇지 않습니다.\n\n### "충분히 좋음" 임계값 정의하기\n\n실시간 그래픽스는 완벽이 아니라 목표를 지속적으로 달성하는 것입니다. 다음과 같은 임계값에 합의하세요:\n\n- 참조 머신에서의 최소 FPS / 최대 프레임 타임\n- 허용 가능한 최악의 스파이크(단지 평균이 아니라)\n- 플랫폼별 메모리 상한\n\n팀이 예를 들어 기준 GPU에서 1080p에 16.6 ms를 목표로 합의하면 논쟁은 구체적이 됩니다: 이 기능이 예산 안에 드는가, 아니면 다른 곳에서 다운그레이드를 강요하는가?\n\n### 되돌릴 수 있는 결정을 선호하라\n\n확신이 서지 않을 때는 되돌릴 수 있는 옵션을 선택하세요:\n\n- 위험한 효과는 기능 플래그로\n- 비용과 실제 연동되는 확장 가능한 설정(낮음/중간/높음) 제공\n- 구형 하드웨어를 위한 폴백 경로\n\n되돌릴 수 있음은 일정 보호 수단입니다. 안전한 경로로 출시하고 야심 찬 기능은 토글 뒤에 숨겨 둘 수 있습니다.\n\n### 사용자가 느낄 수 있는 것을 최적화하라\n\n보이지 않는 개선을 과도하게 공학하는 것을 피하세요. 평균 1% 개선은 한 달의 복잡성 노력 값이 아닌 경우가 많습니다—단, 그 개선이 버벅임을 없애거나 입력 지연을 줄이거나 심각한 메모리 크래시를 방지한다면 예외입니다. 플레이어가 즉시 느끼는 변경을 우선하고 나머지는 기다리게 하세요.\n\n## 엔지니어링 규율: 정확성이 속도를 가능하게 한다\n\n프로그램이 옳을 때 성능 작업은 훨씬 쉬워집니다. 놀랍게도 많은 “최적화” 시간은 실제로 성능 문제처럼 보이는 정확성 버그를 쫓는 데 쓰입니다: 중복 작업으로 인한 우연한 O(N²) 루프, 플래그 미리 설정되지 않아 두 번 실행되는 렌더 패스, 서서히 프레임 타임을 증가시키는 메모리 누수, 무작위 버벅임으로 이어지는 레이스 컨디션 등.\n\n### 정확성을 성능 도구로써 다뤄라\n\n안정적이고 예측 가능한 엔진은 깨끗한 측정을 제공합니다. 동작이 실행 간 변화하면 프로파일을 신뢰할 수 없고 노이즈를 최적화하게 됩니다.\n\n규율 있는 엔지니어링 관행이 속도를 돕습니다:\n\n- 명확한 불변성: 항상 참이어야 하는 것을 정의하세요(예: “각 가시 오브젝트는 한 번 제출된다”, “GPU 리소스는 비행 중에 변경되지 않는다”, “프레임 그래프에 사이클이 없어야 한다”).\n- 디버그 빌드에서 검증: 경고와 어서 나올 수 있게 가벼운 검사와 어서 울리는assertion을 추가하세요—깨진 상태가 신비한 히칭으로 변하기 전에. 버퍼 크기, 상태 전환, 프레임당 할당이 알려진 한도 내에 머무는지 검증하세요.\n\n### 성능 버그를 재현 가능하게 만들어라\n\n많은 프레임 타임 스파이크는 "하이젠버그"입니다: 로깅을 추가하거나 디버거로 한 스텝씩 밟으면 사라집니다. 해독제는 재현성입니다.\n\n작고 제어된 테스트 하니스를 만드세요:\n\n- 특정 기능을 격리하는 최소 테스트 장면(섀도잉, 파티클, UI, 스트리밍 등)\n- 고정된 카메라 경로와 스크립트된 입력으로 실행마다 동일하게\n- 설정 고정(해상도, 품질 레벨, 가능하면 고정 타임스텝)으로 변수를 제거\n\n히치가 나타나면 100번 재생할 수 있는 버튼이 있길 원합니다—“10분 후 가끔 발생한다”는 모호한 보고가 아니라.\n\n### 덜 변경하고 더 배우라\n\n속도 작업은 작고 리뷰 가능한 변경에서 이득을 봅니다. 대규모 리팩터는 한 번에 여러 실패 모드를 만들 수 있습니다: 회귀, 새로운 할당, 숨겨진 추가 작업. 작은 diff는 오직 중요한 질문에 답하기 쉽게 만듭니다: 프레임 타임에서 무엇이, 왜 바뀌었는가?\n\n규율은 관료주의가 아니라—측정을 신뢰 가능하게 유지해 최적화를 미신이 아니라 직관적으로 만드는 방법입니다.\n\n## 기계와 함께 일하라: 데이터, 캐시, 오버헤드\n\n실시간 성능은 단순히 “더 빠른 코드”만이 아닙니다. CPU와 GPU가 효율적으로 일할 수 있도록 작업을 배치하는 것입니다. 카맥은 반복해서 단순한 진실을 강조했습니다: 기계는 문자 그대로입니다. 예측 가능한 데이터를 사랑하고 피할 수 있는 오버헤드를 싫어합니다.\n\n### 데이터 지향 사고: 메모리를 읽기 쉽게 만들어라\n\n현대 CPU는 매우 빠릅니다—메모리를 기다리는 순간을 제외하면. 데이터가 작은 객체들에 흩어져 있으면 CPU는 포인터를 쫓느라 수학적 계산을 하지 못합니다.\n\n유용한 비유: 열 개의 물건을 사러 열 번 장보러 가지 말고 한 카트에 담아 한 번에 가는 것입니다. 코드에서는 자주 쓰이는 값을 가까이(종종 배열이나 빽빽한 struct)에 두어 한 캐시 라인 로드로 실제로 사용할 데이터를 많이 가져오게 하세요.\n\n### 할당 패턴: 작은 churn이 큰 비용이 된다\n\n빈번한 할당은 숨겨진 비용을 만듭니다: 할당자 오버헤드, 메모리 단편화, 시스템이 정리해야 할 때의 예측 불가능한 일시정지. 각 할당이 “작다” 하더라도 지속적인 스트림은 프레임당 세금이 됩니다.\n\n일반적 해결책은 의도적으로 지루합니다: 버퍼 재사용, 객체 풀, 핫 경로에는 장기 할당 선호. 목표는 영리함이 아니라 일관성입니다.\n\n### 배칭: 수학 최적화 전 오버헤드를 줄여라\n\n상당한 프레임 타임이 북키핑에 사라질 수 있습니다: 상태 변경, 드로우 콜, 드라이버 작업, 시스템콜, 스레드 조정.\n\n배칭은 렌더링과 시뮬레이션의 "한 카트" 버전입니다. 많은 작은 작업을 발행하는 대신 유사한 작업을 그룹화해 비싼 경계를 적게 넘으세요. 종종 오버헤드를 줄이는 것이 셰이더나 내부 루프를 미세 조정하는 것보다 프레임 타임 개선에 더 효과적입니다.\n\n## 단순성은 성능 전략이다\n\n성능 작업은 더 빠른 코드뿐 아니라 더 적은 코드에 관한 것이기도 합니다. 복잡성은 매일 치러야 할 비용을 낳습니다: 버그 원인 규명이 느려지고, 수정은 더 많은 테스트가 필요하고, 반복 속도는 느려지며, 드물게 쓰이는 경로에서 회귀가 스며듭니다.\n\n### 복잡성의 숨은 세금\n\n"영리한" 시스템은 우아해 보일 수 있지만 마감일 직전에 특정 맵, 특정 GPU, 특정 설정 조합에서만 프레임 스파이크가 나타나는 경우가 있습니다. 추가되는 기능 플래그, 폴백 경로, 특수 케이스는 이해하고 측정해야 할 동작 수를 곱합니다. 이 복잡성은 개발자 시간 낭비뿐 아니라 런타임 오버헤드(추가 분기, 할당, 캐시 미스, 동기화)를 초래하고, 이는 너무 늦게 드러납니다.\n\n### 설명 가능한 솔루션을 선호하라\n\n좋은 규칙: 성능 모델을 팀원에게 몇 문장으로 설명할 수 없다면 아마도 신뢰성 있게 최적화할 수 없습니다.\n\n단순한 솔루션의 이점 두 가지:
자주 묻는 질문
왜 이 글은 FPS 대신 프레임 타임(ms)을 강조하나요?
프레임 타임은 밀리초(ms) 단위로 측정되는 "프레임당 시간"이며 CPU/GPU가 수행한 작업량과 직접적으로 연결됩니다.
FPS는 역수이기 때문에 변동성을 숨길 수 있습니다.
프레임 타임은 버벅임을 드러냅니다(예: 가끔 발생하는 40–120 ms 프레임) 심지어 평균 FPS가 좋아 보여도요.
예산 세우기가 더 쉽습니다: 16.6 ms = 60 FPS, 33.3 ms = 30 FPS.
프로젝트에 실용적인 프레임 타임 예산은 어떻게 설정하나요?
목표(예: 60 FPS)를 정하고 이를 엄격한 마감시간(16.6 ms)으로 변환하세요. 그런 다음 그 시간을 명시적 예산으로 나눕니다.
예시 출발점:
CPU: 약 7 ms
GPU: 약 9 ms
오버헤드 버퍼: 약 0.6 ms
플랫폼, 해상도, 발열(thermals), 입력 지연 목표에 따라 조정하며, 이를 제품 요구사항으로 취급하세요.
최소한의 프로파일링 셋업은 무엇이어야 하나요?
먼저 테스트를 반복 가능하게 만들고, 아무 것도 변경하기 전에 측정하세요.
고정된 장면 + 고정된 카메라 경로를 사용하세요.
CPU 타임라인 + GPU 타임라인을 캡쳐하세요.
보조 카운트(드로우 콜, 삼각형 수, 할당, 스트리밍 이벤트 등)를 기록하세요.
시간이 실제로 어디에 쓰이는지 알기 전에는 무엇을 최적화할지 결정하지 마세요.
빠르게 CPU 바운드인지 GPU 바운드인지 알아내려면 어떻게 하나요?
제한 요소를 분리하기 위한 빠른 실험을 실행하세요:
해상도 하향: 큰 개선이 있다면 보통 GPU/픽셀 한계입니다.
기능 토글: 그림자, SSR, AO, 파티클 등을 하나씩 끄면서 프레임 타임이 크게 변하는 항목을 찾으세요.
CPU 프로파일러와 GPU 캡처로 확인하세요.
우선 지배적 비용(밀리초 단위)을 정확히 명명할 수 있을 때까지 시스템을 재작성하지 마세요.
왜 프레임 타임 스파이크(꼬리 지연)가 평균 FPS보다 더 중요한가요?
사용자는 평균이 아니라 최악의 프레임을 체감합니다.
다음 항목을 추적하세요:
백분위수(p95, p99, p99.9)로 꼬리 지연을 노출하세요.
히스토그램으로 클러스터와 이상치를 보세요.
이벤트 연관(GC 시작, 셰이더 컴파일, 에셋 로드)을 통해 스파이크를 속성화하세요.
평균이 16.6 ms여도 가끔 80 ms로 튄다면 경험은 여전히 나쁠 것입니다.
버벅임과 히칭을 줄이는 실용적인 방법은 무엇인가요?
비싼 작업을 예측 가능하고 계획적으로 만드세요:
사전계산: 셰이더를 오프라인에서 빌드하고 데이터를 베이크하세요.
워밍업: 로딩 화면이나 제어된 워밍업 장면에서 셰이더와 파이프라인을 컴파일/생성하세요.
분산 처리(암타이즈): 스트리밍, 압축 해제, 업로드를 여러 프레임에 나눠 수행하세요.
프레임당 작업 상한 설정(예: “이 프레임에서 스트리밍 최대 2 ms”).
스파이크를 기록해 재현 가능한 상태로 만들어 고치세요. 단순히 ‘사라지길 바라는’ 방식은 통하지 않습니다.
시각 품질, 성능, 복잡성 사이에서 어떻게 결정하나요?
숫자와 사용자 영향으로 거래(tradeoff)를 명시하세요.
예를 들어 다음과 같이 말하세요:
“이것은 그림자 부드러움을 얻기 위해 GPU 0.4 ms와 VRAM 80 MB를 추가합니다.”
그런 다음 합의된 임계값(참조 하드웨어에서의 최대 프레임 타임, 허용 가능한 최악의 스파이크, 플랫폼별 메모리 한도)에 따라 결정하세요. 확실치 않으면 되돌릴 수 있는 결정(기능 플래그, 품질 단계)을 선호하세요.
왜 정확성(정상 동작)이 성능 작업에서 그렇게 중요한가요?
동작이 안정적이어야 성능 측정이 신뢰할 수 있습니다.
실무적 단계:
불변성 정의(예: “각 가시 오브젝트는 한 번만 제출되어야 한다”).
디버그 검증 추가(할당 한도, 상태 전환 검증).
결정적 재현 하니스 구축(최소 장면, 스크립트된 입력).
실행 간 동작이 바뀌면 프로파일은 노이즈를 최적화하게 만듭니다.
"기계와 협력하기"(캐시, 데이터, 배칭)는 실무에서 무엇을 의미하나요?
많은 경우 “빠른 코드”란 사실상 “메모리와 오버헤드”를 다루는 일입니다.
중점 사항:
데이터 지역성: 핫 데이터는 연속적으로 배치해 캐시 미스를 줄이세요.
할당 제어: 버퍼를 재사용하고 객체 풀을 사용해 프레임당 할당을 피하세요.
배칭: 드로우 콜/상태 변경/동기 지점을 줄여 오버헤드를 감소시키세요.
오버헤드를 줄이는 것이 내부 루프를 미세 최적화하는 것보다 더 큰 이득을 주는 경우가 많습니다.
프로젝트가 발전하면서 성능 회귀를 어떻게 방지하나요?
성능을 측정 가능하고 반복 가능하며 실수로 깨기 어렵게 만드세요.
소수의 기준 장면(CPU-무거운 장면, GPU-무거운 장면, 최악 케이스)을 유지하세요.
고정 하드웨어/구성에서 테스트하고 결과를 커밋 해시와 함께 보관하세요.
p50/p95/p99 프레임 타임, 최대 메모리, 로딩 시간을 추적하세요.
임계값(예: p95가 5% 이상 악화되면 경고)을 설정하세요.
회귀가 나타나면 , 소유자 지정, 릴리스 차단 시 신속한 를 하세요.
실시간 그래픽스를 위한 존 카맥의 성능 마인드셋 | Koder.ai
프로파일링하고 추론하기 쉽다(변수가 적다)
작은 변경이 뜻밖의 느려짐을 초래하는 "알 수 없는 알 수 없음"을 줄인다\n\n### 코드 삭제는 진짜 최적화 도구다\n\n때로 가장 빠른 길은 기능을 제거하거나 옵션을 줄이거나 여러 변형을 하나로 합치는 것입니다. 기능이 적어지면 코드 경로가 줄고 상태 조합이 줄며 성능이 은밀히 악화될 장소가 줄어듭니다.\n\n코드 삭제는 품질 측면에서도 옳은 선택입니다: 제거된 모듈이 생성할 수 있는 버그는 근본적으로 사라집니다.\n\n### 리팩터 vs 패치: 빠른 결정 체크리스트\n\n패치(외과적 수정)를 선택할 때:\n\n- 특정 핫 패스를 확인했고 작은 변경이 측정 가능하게 개선할 때\n- 시스템이 안정적이고 광범위하게 사용되어 아키텍처 변경이 새로운 회귀를 유발할 위험이 있을 때\n- 현재 릴리스 일정에 맞는 안전한 개선이 필요할 때\n\n리팩터(구조 단순화)를 선택할 때:\n\n- 프로파일링이 많은 호출 지점이나 레이어에 걸쳐 오버헤드를 가리킬 때\n- 관련 없는 변경 후에도 같은 영역에서 성능이 반복적으로 깨질 때\n- 코드를 안전하게 수정하려면 부족한 암묵 지식(tribal knowledge)이 필요할 때\n- 경로를 삭제하거나 병합해 개념 수를 줄일 수 있을 때\n\n단순성은 “덜 야망적”이 아닙니다. 성능이 가장 중요한 순간—압박이 있을 때—이해 가능한 설계를 선택하는 것입니다.\n\n## 회귀 방지: 성능을 습관으로 만들라\n\n성능 작업은 그것이 미끄러졌을 때 알 수 있어야만 유지됩니다. 그것이 바로 성능 회귀 테스트입니다: 새 변경이 제품을 더 느리게, 덜 부드럽게, 또는 메모리를 더 많이 쓰게 만드는지 감지하는 반복 가능한 방법입니다.\n\n기능 테스트가 “동작하나?”를 묻는 반면 회귀 테스트는 “여전히 같은 속도로 느껴지나?”를 답합니다. 빌드가 100% 기능적일지라도 프레임 타임을 4 ms 추가하거나 로딩을 두 배로 만들면 나쁜 출시입니다.\n\n### 실무에서 실제로 쓰이는 가벼운 워크플로우\n\n랩이 없어도 시작할 수 있습니다—단지 일관성이 필요합니다.\n\n작업을 대표하는 소수의 기준 장면을 선택하세요: 하나는 GPU-무거운 뷰, 하나는 CPU-무거운 뷰, 하나는 최악의 스트레스 장면. 이들은 안정적이고 스크립트화되어 카메라 경로와 입력이 동일하게 반복됩니다.\n\n고정된 하드웨어(알려진 PC/콘솔/개발킷)에서 실행하세요. 드라이버, OS, 클럭 설정을 변경하면 기록하세요. 하드웨어/소프트웨어 조합을 테스트 픽스처의 일부로 취급하세요.\n\n결과를 버전별 히스토리로 보관하세요: 커밋 해시, 빌드 구성, 머신 ID, 측정된 지표. 목표는 완벽한 숫자가 아니라 신뢰할 수 있는 추세선입니다.\n\n### CI 친화적 메트릭 추천\n\n논쟁하기 어려운 메트릭을 선호하세요:\n\n- 프레임 타임 백분위수(p50/p95/p99), 평균 FPS만이 아니라. 백분위수는 버벅임과 장기 꼬리를 드러냅니다.\n- 최대 메모리(및 할당 스파이크). 메모리 누수는 종종 충돌 전에 나타납니다.\n- 로딩 시간(콜드 스타트 및 레벨 전환), 플레이어는 초 단위 차이를 더 민감하게 느낍니다.\n\n간단한 임계값을 정의하세요(예: p95 프레임 타임은 5% 이상 회귀하면 안 됨).\n\n### 회귀를 잡았을 때 해야 할 일\n\n회귀를 버그처럼 다루고 오너와 기한을 정하세요.\n\n먼저 바이섹트로 회귀를 도입한 변경을 찾으세요. 회귀가 릴리스를 차단하면 신속히 리버트하고 수정과 함께 재적용하세요.\n\n수정 후에는 가드레일을 추가하세요: 테스트를 유지하고 코드에 주석을 달며 기대 예산을 문서화하세요. 습관 자체가 승리입니다—성능은 나중에 하는 일이 아니라 유지하는 것이 됩니다.\n\n## 복잡한 시스템 출시: 성능, 마감, 현실\n\n"출시"는 달력 이벤트가 아니라 엔지니어링 요구사항입니다. 랩에서만 잘 돌아가거나 수작업 조정 후에만 프레임 타임을 맞추는 시스템은 끝난 것이 아닙니다. 카맥의 마인드셋은 현실 세계 제약(하드웨어 다양성, 지저분한 콘텐츠, 예측 불가능한 플레이어 행동)을 처음부터 규격(spec)의 일부로 취급합니다.\n\n### 출시란 무엇이 참이어야 하는지를 선택하는 것\n\n출시에 가까워지면 완벽보다 예측 가능성이 더 가치 있습니다. 비협상 항목을 평범한 용어로 정의하세요: 목표 FPS, 최악의 프레임 타임 스파이크, 메모리 한계, 로드 시간. 그런 다음 이를 위반하는 모든 것을 버그로 취급하세요—"다듬기"가 아니라. 이것은 성능 작업을 선택적 최적화가 아니라 신뢰성 작업으로 재구성합니다.\n\n### 플레이어가 실제로 느끼는 것을 우선하라\n\n모든 느려짐이 동일하게 중요한 것은 아닙니다. 사용자에게 직접적으로 보이는 문제를 먼저 고치세요:\n\n- 버벅임과 긴 스파이크는 보통 일정하게 조금 느린 렌더링보다 체감 품질에 더 큰 영향을 줍니다.\n- 메뉴 히칭, 스트리밍 팝, 입력 지연은 평균 FPS의 작은 감소보다 경험에 더 큰 해를 줍니다.\n- 일반적인 시나리오(격렬한 전투, 카메라 회전, 이펙트가 많은 순간)에서의 회귀는 희귀한 코너 케이스보다 우선순위가 높습니다.\n\n프로파일링 규율은 여기서 빛을 발합니다: “큰 것처럼 보이는 이슈”를 짚는 것이 아니라 측정된 영향에 근거해 선택합니다.\n\n### 변경을 단계적으로 적용하고 안전을 기본값으로 하라\n\n출시 직전의 성능 작업은 위험합니다—"수정"이 새로운 비용을 도입할 수 있으므로. 단계적 롤아웃을 사용하세요: 먼저 계측을 추가하고 변경은 토글 뒤에 넣고 노출을 점차 넓히세요. 프레임 타임을 보호하는 성능 안전 기본값(시각 품질을 약간 낮출 수 있는 설정)을 선호하세요—특히 자동 감지 구성에서는 더더욱.\n\n여러 플랫폼/등급을 출시한다면 기본값은 제품 결정으로 취급하세요: 화려함을 약간 줄이는 것이 불안정해 보이는 것보다 낫습니다.\n\n### 비기술 이해관계자에게 제약을 전달하라\n\n거래를 결과로 번역하세요: “이 효과는 중간급 GPU에서 프레임당 2 ms를 추가해 전투 중 60 FPS 이하로 떨어질 위험이 있습니다.” 옵션을 제시하세요, 잔소리 말고: 해상도 낮추기, 셰이더 단순화, 스폰율 제한, 또는 더 낮은 목표 수락. 제약은 명확한 사용자 영향과 함께 제시될 때 수용되기 쉽습니다.\n\n## 오늘 이 마인드셋을 적용할 수 있는 실용적 체크리스트\n\n새 엔진이나 리라이트가 필요하지 않습니다. 카맥 스타일 성능 사고를 채택하려면 반복 가능한 루프가 필요합니다: 성능을 가시화하고, 테스트 가능하게 하고, 우연히 깨기 어렵게 만드는 루프입니다.\n\n### 반복 가능한 루프(측정 → 예산 → 격리 → 최적화 → 검증 → 문서화)\n\n1) 측정: 프레임 타임과 주요 서브시스템의 평균, p95, 최악 스파이크에 대한 기준선을 캡처하세요.\n\n2) 예산: CPU와 GPU(메모리도 빡빡하면)용 프레임당 예산을 설정하세요. 예산을 기능 목표 옆에 적어두세요.\n\n3) 격리: 최소 장면이나 테스트에서 비용을 재현하세요. 재현하지 못하면 고칠 수 없습니다.\n\n4) 최적화: 한 번에 한 가지를 바꾸세요. 작업량을 줄이는 변경을 선호하세요, 단순히 "더 빠르게 만들기"가 아니라.\n\n5) 검증: 재프로파일, 델타 비교, 품질 회귀와 정확성 문제 점검.\n\n6) 문서화: 무엇을 바꿨고 왜 도움이 되었는지, 앞으로 무엇을 감시할지 기록하세요.\n\n### 즉시 적용할 수 있는 경험 법칙\n\n- 가장 큰 바를 최적화하라, 가장 짜증나는 추측을 최적화하지 마라.
사용자가 버벅임을 느낀다면 평균보다 스파이크를 먼저 추적하라.
비용을 설명할 수 없다면 아직 그 기능을 소유한 것이 아니다.
희귀한 최악 폭발보다 예측 가능한 비용을 선호하라.
새 작업은 사전에 예산을 잡아라(CPU ms, GPU ms, 메모리, 대역폭).\n- 객체당/프레임당 숨겨진 루프를 피하라(콘텐츠와 함께 선형적으로 증가하는 것들).\n- 성능 테스트를 "완료"의 일부로 만들어라, 출시 직전 급조가 아니라.\n\n### 병합 전 간단한 “성능 리뷰” 템플릿\n\n- 기능 요약: 무엇이 바뀌었는가, 무엇을 가능하게 하는가\n- 대상 플랫폼 및 설정: (예: 콘솔 퍼프모드, 중급 PC)\n- 예산: CPU __ ms, GPU __ ms, 메모리 __ MB\n- 기준 vs 이후: 평균 / ms, p95 / ms, 최악 스파이크 / ms\n- 병목 가정: CPU인가 GPU인가? 근거:\n- 테스트 장면 및 재현 단계:\n- 위험 및 가드레일: 무엇이 회귀할 수 있는가, 어떤 메트릭이 경고하는가\n- 롤백 계획: 어떻게 비활성화하거나 우아하게 저하할 수 있는가\n\n### Koder.ai가 이 워크플로우에 맞는 위치\n\n이 습관들을 팀 전체에 운영화하려면 마찰을 줄이는 것이 핵심입니다: 빠른 실험, 반복 가능한 하니스, 쉬운 롤백.\n\nKoder.ai는 엔진 자체가 아니라 주변 툴링을 구축할 때 도움을 줄 수 있습니다. 실제로 내보낼 수 있는 소스 코드를 생성하는 vibe-coding 플랫폼이기 때문에 내부 대시보드(프레임 타임 백분위수, 회귀 이력, "성능 리뷰" 체크리스트)를 빠르게 띄우고 채팅으로 요구사항을 진화시키며 스냅샷과 롤백을 통해 "한 가지 변경 → 재측정" 루프를 실용적으로 맞출 수 있습니다.\n\n더 실용적 가이드가 필요하면 /blog 를 둘러보거나 /pricing 에서 팀들이 이를 어떻게 운영화하는지 확인하세요.