Quick Reference
- 캐싱 대상은 매 frame 또는 다수 오브젝트에서 반복되고 Profiler에 실제 비용·
GC.Alloc으로 보이는 작업입니다. 초기화에서 한 번 쓰는 lookup은 보통 캐시할 이유가 없습니다. - component reference는
Awake또는 의존성이 바뀌는 bind 시점에 얻고, scene search와 allocation형 query는 재사용 buffer 또는 등록 목록으로 바꿉니다. 값이 교체될 수 있으면 캐시를 갱신하는 소유자를 반드시 둡니다. - 문자열 hash와 component reference는 비용을 줄이지만 결과를 자동으로 최신화하지 않습니다. 캐시가 stale하면 빠르게 잘못된 오브젝트를 조작합니다.
csharp
private Rigidbody body;
private void Awake()
{
if (!TryGetComponent(out body))
enabled = false;
}
private void FixedUpdate()
{
body.AddForce(Vector3.forward, ForceMode.Acceleration);
}비용을 확인할 위치
- CPU Usage > Hierarchy/Timeline에서 반복되는
BehaviourUpdate, 검색 메서드, UI refresh를 같은 재현 구간에서 봅니다. 한 번만 호출한GetComponent보다 수백 개가 같은 frame에 호출한 search가 먼저 대상입니다. - GC.Alloc은 매 frame 새 배열·문자열·boxing·closure가 만들어지는 신호입니다. allocation이 보이면 Call Stacks로 LINQ, 문자열 보간,
GetComponents<T>()결과 배열,Physics.RaycastAll같은 실제 호출 경로를 확인합니다. GetComponent<T>()는 해당 GameObject에 이미 있는 component를 가져올 때 씁니다.TryGetComponent(out T)는 component가 없을 수 있는 초기화에서 null 검사와 함께 쓰기 좋습니다. 둘 다 매 frame 호출을 금지하는 규칙이 아니라, 반복 비용이 측정된 경로에서 참조를 보관할 후보입니다.GetComponents<T>(),FindObjectsByType,Physics.RaycastAll처럼 결과 collection을 돌려주는 API는 호출 빈도와 allocation을 함께 봅니다. 크기가 고정된 hot path라면GetComponents(List<T>), 등록 목록,RaycastNonAlloc처럼 caller가 보관하는 buffer를 검토합니다. buffer가 부족하면 결과가 잘리는 API는 capacity 도달을 별도로 계측해야 합니다.
캐시 수명과 스크립트 연결
csharp
using Unity.Profiling;
using UnityEngine;
public sealed class TargetTracker : MonoBehaviour
{
private static readonly ProfilerMarker RefreshMarker =
new("TargetTracker.Refresh");
private Transform target;
public void Bind(Transform nextTarget)
{
target = nextTarget;
}
private void Update()
{
using (RefreshMarker.Auto())
{
if (target == null)
return;
transform.LookAt(target.position);
}
}
}Awake캐시는 component가 같은 GameObject에 계속 남는 경우에 맞습니다. prefab이 재구성되거나 component를 destroy/add할 수 있는 구조라면OnEnable,Bind,OnValidate같은 갱신 지점을 정합니다. Unity의 destroyed object는 C# null처럼 비교되므로, 참조가 유효하다고 가정한 채 사용하지 않습니다.Animator.StringToHash와Shader.PropertyToID는 같은 parameter/property를 반복 접근할 때 문자열 lookup을 ID로 바꿉니다. ID는 실행 중에만 쓰고, asset이나 save file에 영구 값으로 저장하지 않습니다. Animator/Shader ID의 수명과 material 선택은 해시 ID에서 이어집니다.ProfilerMarker.Auto()는 cache 전후를 같은 marker로 비교할 때 유용합니다. marker 자체도 비용이 있으므로 아주 작은 inner loop마다 붙이지 말고, 적을 줄일 시스템 경계에만 둡니다.- 캐시를 공유하는 static dictionary나 object pool은 등록과 해제를 한 owner가 맡아야 합니다.
OnDisable에서 제거해야 할 대상이 남으면 destroyed object reference와 메모리 누수가 쌓이고, 재활성화 때 중복 등록하면 같은 작업이 여러 번 수행됩니다.
자주 틀리는 부분
“캐시”가 “항상 더 빠름”을 뜻하지는 않습니다. 한 번만 쓰는 값을 field로 늘리면 lifecycle과 null 경로만 복잡해집니다. release-like build의 Profiler에서 반복 비용이나 allocation을 확인한 뒤 바꾸세요.
캐시가 가리키는 대상의 교체를 놓치면 성능 문제보다 더 위험한 상태 버그가 됩니다. target 교체·pool return·scene unload가 가능한 구조에는 Bind/Unbind 또는 해제 callback을 두고, component reference를 새로 얻는 시점도 그 계약에 포함하세요.
참고 링크
3 sources