CPU 캐시를 최대한 활용할 수 있도록 데이터를 배치해 메모리 접근 속도를 높이자.
CPU의 성능은 시간이 갈수록 증가했지만,
CPU가 필요한 데이터를 RAM으로부터 버스를 통해 가져오는 시간은 그에 비해 빨라지지 못했다.
따라서 캐싱을 통해 이를 어느정도 해결했다.
그러나 여전히 캐시 미스가 발생하면 CPU는 데이터가 도착할 때까지 수백 사이클을 기다리게 된다.
같은 일을 하더라도 캐시 미스가 발생하는 횟수를 줄여보자
데이터를 처리하는 순서대로 연속된 메모리에 둘수록, 즉 데이터 지역성을 높일수록
캐시를 통해서 성능을 향상할 수 있다.
추상화를 위한 인터페이스의 경우 포인터나 레퍼런스를 통해 객체에 접근하게 된다.
하지만 포인터는 메모리를 여기저기 찾아가야 하기 때문에 캐시 미스가 발생한다.
따라서 데이터 지역성과 추상화 사이의 절충이 필요하다.
게임루프와 컴포넌트 패턴을 사용한 예제
while (!gameOver) {
for (int i = 0; i < numEntities; ++i)
entities[i]->ai()->update();
for (int i = 0; i < numEntities; ++i)
entities[i]->physics()->update();
for (int i = 0; i < numEntities; ++i)
entities[i]->render()->update();
}
-> 결국 컴포넌트 하나를 업데이트할 때마다 캐시미스가 발생할 가능성이 높다.
AIComponent* aiComponents = new AIComponent[MAX_ENTITIES];
PhysicsComponent* physicsComponents = new PhysicsComponent[MAX_ENTITIES];
RenderComponent* renderComponents = new RenderComponent[MAX_ENTITIES];
while (!gameOver) {
for (int i = 0; i < numEntities; ++i)
aiComponents[i].update();
for (int i = 0; i < numEntities; ++i)
physicsComponents[i].update();
for (int i = 0; i < numEntities; ++i)
renderComponents[i].update();
}
-> 앞선 예제에 비해 업데이트 루프가 50배 빨라진다.
void particlsSystem::update() {
for (int i = 0; i < numParticles_; ++i)
if (particles_[i].isActive()) {
particles_[i].update();
}
}
}
-> 활성 여부를 플래그로 검사하지 말고, 활성 파티클만 맨 앞으로 모아두자!
// 파티클 활성화: 맨 앞 비활성 파티클과 바꾸기
void ParticleSystem::activeParticle(int index) {
assert(index >= numActive_);
Particle temp = particles_[numActive_];
particles_[numActive_] = particles_[index];
particles_[index] = temp;
numActive_++;
}
// 파티클 비활성화: 마지막 활성 파티클과 바꾸기
void ParticleSystem::deactiveParticle(int index) {
assert(index < numActive_);
numActive_--;
article temp = particles_[numActive_];
particles_[numActive_] = particles_[index];
particles_[index] = temp;
}
단, Particle
클래스가 자신의 활성 상태를 스스로 제어할 수 없고, 직접 activate
과 같은 메서드를 호출할 수가 없다.
반드시 ParticleSystem
을 거쳐야만 한다.
class AIComponent {
public:
void udpate() { /* ... */ }
private:
Animation* animation_;
double energy_;
Vector goalPos_;
LootType drop_;
int minDrops_;
int maxDrops_;
double chanceOfDrop_;
};
-> 이렇게 자주 사용하지 않는 데이터는 따로 모아두자.
class AIComponent {
public:
// ...
private:
// 빈번한 데이터
Animation* animation_;
double energy_;
Vector goalPos_;
// 한산한 데이터
LootDrop* loot_;
};
class LootDrop {
private:
friend class AIComponent;
LootType drop_;
int minDrops_;
int maxDrops_;
double chanceOfDrop_;
};
-> AI 컴포넌트를 매 프레임마다 순회할 때 실제로 사용할 데이터만 캐시에 올라간다.(LootDrop*
포인터 제외)
※어느 데이터가 자주 사용되는지를 나누기는 굉장히 애매하므로, 연습이 필요하다.