캐시(Cache) 라고 하는 좀 더 빠른 메모리 영역으로 데이터를 가져와서 접근하는 방식.
예를 들어, 속도가 느린 하드 디스크의 데이터를 메모리로 가지고 와서 메모리 상에서 읽기 쓰기를 수행하는 것을 "데이터를 메모리에 캐싱한다" 라고 한다.
캐시는 데이터의 지역성이라는 특성을 이용해서 성능 개선을 달성한다.
지역성은 공간지역성과 시간지역성이 존재하는데,
공간 지역성은 한번 접근한 데이터의 인근에 저장되어 있는 데이터가 다시 접근할 가능성이 높은 특성을 의미한다
시간 지역성은 한번 접근한 데이터가 가까운 시간내에 다시 접근될 가능성이 높다는 의미다.
이러한 데이터 접근의 지역성이라는 특성을 이용해서 자주 접근될 데이터를 더 빠른 속도의 메모리 상에 가지고 와서 연산을 수행하여 성능을 높이는 것이 캐싱의 목표다.
반복 계산 / 조회 비용을 없애기 위해 결과를 한 번 구해서 재사용 하는 것
1. 컴포넌트/참조 캐싱
// 나쁜 예: Update마다 GetComponent 호출 (내부적으로 탐색 비용 발생)
void Update() {
GetComponent<Rigidbody>().AddForce(Vector3.up);
}
// 좋은 예: Awake/Start에서 한 번만 캐싱
Rigidbody rb;
void Awake() { rb = GetComponent<Rigidbody>(); }
void Update() { rb.AddForce(Vector3.up); }
2. 오브젝트 풀링(object Pooling)
생성과 삭제는 메모리 할당&해제 비용이 크고 GC를 유발하는데 자주 생성 소멸하는 오브젝트는 미리 만들어 놓고 SetActive로 껐다 켰다 하면서 재사용한다.
3. Shader Property ID 캐싱
// 매번 문자열 해싱 비용 발생
material.SetFloat("_Alpha", value);
// 캐싱해서 정수 비교로 처리
static readonly int AlphaID = Shader.PropertyToID("_Alpha");
material.SetFloat(AlphaID, value);
꼭 쉐이더가 아니더라도 const or readonly로 선언했던 변수들을 다시 사용하는 것도 이 상황에 해당된다고 본다.
또는
// 매 호출마다 새 객체 생성 → GC 압박
yield return new WaitForSeconds(1f);
// static으로 캐싱해서 재사용
static readonly WaitForSeconds wait1s = new WaitForSeconds(1f);
yield return wait1s;
new로 생성하는 waitforseconds들도 캐싱해서 사용할 수 있다
**4. 데이터/에셋 캐싱
버퍼링은 버퍼(buffer) 라고 하는 메모리 영역에 데이터를 모았다가 사용하는 동작을 의미한다.
버퍼를 사용하는 이유로는 크게 3가지를 꼽아볼 수 있다.
보내는 쪽과 받는 쪽의 속도차이에서 오는 비효율을 극복하기 위함이다.
cpu를 통해 데이터를 다루다가 다시 디스크로 쓰기 연산을 하려고 할 때, 매번 디스크 드라이브에 데이터를 넘기고 디스크로 쓰여지기를 기다린다면 CPU는 메모리에 비해 속도가 매우 느린 디스크의 동작을 항상 기다리게 된다. 버퍼링을 하게 되면, 디스크로의 작업을 버퍼에 '모아서' 한번에 수행하기 때문에 보다 효율적으로 수행할 수 있다.
서로 다른 자료 전송 사이즈가 다른 상황을 극복하기 위함이다.
작은 데이터들을 하나에 모아서(버퍼링) 하나의 블록으로 전달 할 수 있다.
데이터 입출력의 의미를 명확하게 하기 위해 사용된다.
특정 데이터가 생성되고 다른 쪽으로 이동한 상황에서,
buffer에 저장하지 않고 보내면
받은 쪽 입장은 생성된 시점의 데이터로 받아야 하는지 전달받은 시점의 데이터로 받아야 하는지 불확실하다 .
buffer에 담아서 보내면 buffer에 담은 시점을 기준으로 입출력을 시행할 수 있다.
관련 예시를 찾긴했는데 이해하지 못해서 풀어서 쓴다
처리 속도 불일치 해결 및 흐름 제어
버퍼에 담긴 데이터는 처리된 후 폐기된다.
Frame Buffer / Render Target : 화면에 그려질 픽셀 데이터를 담는 버퍼
Depth Buffer : 깊이 판정, 마스킹 용
Double Triple Buffering : 화면 찢어짐(tearing) 방지를 위해 그리는 버퍼와 보여주는 버퍼를 분리
Command Buffer : CommandBuffer 클래스로 랜더링 명령을 모아뒀다가 한 번에 GPU에 제출 (커스텀 렌더 이펙트에 자주 사용)
ComputeBuffer : GPU와 대량 데이터를 주고 받을 때 (파티클 등)
입력 버퍼(Input Buffering)
액션/격투 게임에서 사용자가 입력한 커맨드를 짧은 시간 동안 버퍼에 저장해뒀다가, 다음 유효한 타이밍에 실행 (ex: 점프 직전 눌러도 착지하자마자 점프되게)
순환 버퍼
고정 크기 배열을 재사용하며 오래된 데이터를 덮어쓰는 구조.
로그 기록, 리플레이 시스템, 이동 히스토리 등에 사용.
힙 할당없이 고정 메모리로 반복 데이터를 다룰 수 있어 GC 친화적이다.
String Builder = 문자열 버퍼
// 매번 새 문자열 생성 → GC 압박
string result = "";
for (int i = 0; i < 100; i++) result += i;
// 버퍼에 누적 후 한 번에 변환
var sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.Append(i);
string result = sb.ToString();
캐싱 : 이미 확정된 결과를 재계산, 재요청 방지로 저장
버퍼 : 아직 처리 전인 데이터를 속도 완충을 위해서 저장
그동안 운영체제에 중요성을 못 느끼고 있었는데, 단순한 구조 구현 뿐 아니라 그 너머의 system이나 tool을 만들려면 관련 지식도 어느정도 알고 있어야 한다고 생각이 바뀌었다.
모르는 건 차근히 공부해가자