핵심 정리
csharp
private Renderer cachedRenderer;
private void Awake()
{
cachedRenderer = GetComponent<Renderer>();
}구조 이해
- 핫패스 최적화의 핵심은 "비싼 API를 없애라"가 아니라 "프레임마다 반복되는 일을 줄여라"입니다. 작은 비용도 수백 오브젝트와 매 프레임 반복이 겹치면 충분히 병목이 됩니다.
- 가장 흔한 첫 단계는
GetComponent,Find, 자주 쓰는Transform,Renderer,Animator참조를Awake나Start에서 캐싱하는 것입니다. 이건 성능뿐 아니라 의존 관계를 코드에 드러내는 효과도 있습니다. - 두 번째 단계는 lookup 패턴을 줄이는 것입니다. 문자열 기반 animator parameter, shader property, tag 비교, 반복 검색 같은 구조는 hash나 직접 참조로 바꾸면 비용과 실수 가능성을 함께 줄일 수 있습니다.
- 세 번째 단계는 GC 할당입니다.
new를 매 프레임 반복하거나, 결과 배열을 새로 만들거나, boxing/closure를 만들면 프레임 드랍이 간헐적으로 튈 수 있습니다. 그래서 할당형 API 대신 재사용 가능한 buffer와 명시적 캐시를 보는 습관이 중요합니다. - 다만 이 카드의 목표는 "모든 것을 캐싱하라"가 아닙니다. profiler에서 반복 호출과 allocation이 보이는 핫패스부터 줄이는 것이 맞고, 한 번 호출되는 초기화 코드는 과하게 손대지 않는 편이 더 낫습니다.
체크포인트
| 상황 | 적합한 선택 |
|---|---|
Update에서 GetComponent 반복 | Awake에서 한 번 캐싱 |
| Animator/Shader 속성을 문자열로 매 프레임 접근 | StringToHash/PropertyToID로 ID 캐싱 |
Physics.RaycastAll 결과 배열이 매 프레임 생성 | RaycastNonAlloc + 재사용 buffer |
| 람다/클로저를 매 프레임 생성 | 정적 메서드 참조 또는 사전 캐싱 |
| 최적화 전후 효과 확인 | Profiler로 반드시 측정 |
| 캐싱 대상 | 캐싱 가치가 큰 경우 | 과하게 캐싱할 필요가 작은 경우 |
|---|---|---|
| 컴포넌트 참조 | 매 프레임 반복 사용 | 초기화 때 한두 번만 사용 |
| 해시 ID | Animator, Shader 속성을 반복 접근 | 한 번만 설정하고 끝남 |
| 배열/버퍼 | 쿼리 결과를 매 프레임 재사용 | 매우 드문 호출 |
| 계산 결과 | 동일 입력으로 같은 프레임에 반복 호출 | 입력이 계속 바뀌어 재계산이 필수 |
주의할 점
값이 자주 바뀌는 참조를 캐싱하면 stale cache 버그가 생깁니다. 반복 호출 빈도와 값 교체 가능성을 함께 고려하세요.
csharp
// ❌ 자주 교체되는 참조를 캐싱 — stale cache 버그
private Renderer cachedRenderer;
private void Awake()
{
cachedRenderer = GetComponent<Renderer>();
}
// 오브젝트가 교체됐는데 캐시는 이전 Renderer를 가리킴
cachedRenderer.material.color = Color.red; // 엉뚱한 오브젝트 변경
// ✅ 바뀌지 않는 참조만 캐싱, 바뀔 수 있는 참조는 필요 시 Get
private Rigidbody body; // 캐싱 OK — Awake 이후 바뀌지 않음
private void Awake() => body = GetComponent<Rigidbody>();Profiler에 안 잡히는 경로까지 무조건 캐싱하면 코드만 복잡해지고, 값이 바뀌는 경로에서는 stale cache 버그만 늘 수 있습니다. 핫패스와 초기화 코드를 구분하세요.
text
// ❌ 모든 참조를 무조건 캐싱
Start에서 한 번만 쓰는 Transform까지 필드로 보관
→ 이득은 작고 코드만 늘어남
// ✅ 반복 호출과 할당이 보이는 경로부터 캐싱
Update, FixedUpdate, 빈번한 UI refresh, 물리 쿼리 루프참고 링크
3 sources