1. 서론
오늘날 소프트웨어 개발에서 성능은 중요한 요소로 자리 잡고 있다. 어느 시점부터 프로그래밍 언어의 덕목이라 함은 쉬운 사용성에 더 기울어지고 있는 요즘이지만, 그런 것들로 인해 게임 개발, 그래픽 프로그래밍, 실시간 데이터 처리 애플리케이션 등에서 요구하는 고성능 지향의 최적화가 덩달아 용이해지지는 않는다. 다시 말해 아무리 더 넓은 저변의 사용자를 지향하고, 더 편한 사용성, 더 쉬운 개발을 지향하는 언어가 나온다 하여도, 그것으로 개발된 프로그램이 실행되는 환경이 변하지는 않았다. 우리는 여전히 가산기-누산기-레지스터의 구조로부터 만들어진 CPU 위에서 실행되는 프로그램을 만들고 있다.
그렇다고 해서, 어떤 문제를 좀 더 풀기 쉬운 형태의 개념으로 해석하고 풀이하고 고찰하는 것의 가치와 의미가 퇴색되지는 않는다. 그로써 고안된 수많은 프로그래밍 패러다임과 디자인 패턴은 지금도 계속해서 어둠 속을 헤매는 개발자들에게 길잡이 별이 되어주고 있다. 전통적인 객체 지향 설계(Object-Oriented Design, OOD)는 그중에서도 아주 오랜 기간 동안, 그리고 지금까지도 우리가 프로그래밍으로 풀어내고자 하는 문제에 대한 편리한 모델링 도구로써 많은 개발자들에게 사랑받고 있는 개념이다. 그러면서도 동시에 최근에는 데이터 중심 설계(Data-Oriented Design, DOD)가 주목받고 있는 것은 왜일까를 이 시리즈를 통해 고민해보고자 한다.
그 첫 번째로써, 이번 글에서는 우선 OOP와 DOD의 개념을 비교하고, 각각의 장단점과 실제 예제를 통해 기존 프로그래밍 패러다임들에 대해 DOD가 도전하는 영역들을 탐구해보고자 한다.
2. 객체 지향 설계(OOP)의 개념과 활용
2.1 OOP의 기본 개념
객체 지향 프로그래밍(Object-Oriented Programming, OOP)은 현실 세계의 개념을 객체라는 단위로 모델링하여 소프트웨어를 개발하는 프로그래밍 패러다임이다. 객체는 **데이터(속성)**와 해당 데이터를 조작하는 **함수(메서드)**를 함께 묶어 하나의 단위로 관리한다.
OOP의 주요 특징:
- 캡슐화(Encapsulation): 데이터와 함수를 하나의 객체로 묶어 외부로부터 데이터를 보호하고, 객체 내부의 구현 세부 사항을 숨긴다.
- 상속(Inheritance): 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받아 코드 재사용성을 높인다.
- 다형성(Polymorphism): 동일한 인터페이스를 통해 다양한 객체를 처리할 수 있어 코드의 유연성을 높인다.
- 추상화(Abstraction): 복잡한 시스템을 단순화하여 핵심적인 부분에 집중할 수 있게 한다.
2.2 강아지 멍멍 고양이 야옹
OOP의 개념을 한 번이라도 접해본 사람이라면 누구나 익숙한 동물 울음소리를 모델링하는 간단한 프로그램이다.
#include <iostream>
#include <vector>
// 추상 클래스: Animal
class Animal {
public:
// 순수 가상 함수: 각 동물이 자신의 울음소리를 구현해야 함
virtual void makeSound() const = 0;
// 가상 소멸자: 상속받은 클래스의 소멸자를 올바르게 호출하기 위해 필요
virtual ~Animal() = default;
};
// 구체적인 동물 클래스: Dog
class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "멍멍" << std::endl;
}
};
// 구체적인 동물 클래스: Cat
class Cat : public Animal {
public:
void makeSound() const override {
std::cout << "야옹" << std::endl;
}
};
int main() {
// Animal 포인터를 저장하는 벡터
std::vector<Animal*> animals;
// 동적 할당을 통해 객체 생성
animals.push_back(new Dog());
animals.push_back(new Cat());
// 다형성을 활용하여 각 동물의 makeSound() 호출
for (const auto& animal : animals) {
animal->makeSound();
}
// 메모리 해제
for (auto& animal : animals) {
delete animal;
}
return 0;
}
- Animal은 추상 클래스로, makeSound()라는 순수 가상 함수를 가진다.
- Dog와 Cat 클래스는 Animal을 상속받아 makeSound()를 오버라이딩한다.
- main() 함수에서는 Animal* 타입의 포인터를 저장하는 벡터를 사용하여 다양한 동물 객체를 관리한다.
- 루프를 통해 각 동물의 makeSound()를 호출할 때 다형성이 적용되어 실제 객체의 메서드가 호출된다.
2.3 OOP가 넓은 범위에서 유용하게 사용된 이유
- 현실 세계의 모델링 용이성: 현실의 사물과 개념을 객체로 표현하여 프로그래밍에 적용하기 쉽다.
- 코드 재사용성 향상: 상속과 캡슐화를 통해 코드의 중복을 줄이고 유지보수를 용이하게 한다.
- 유연한 코드 구조: 다형성을 통해 새로운 기능 추가나 변경에 유연하게 대응할 수 있다.
- 캡슐화를 통한 안정성 확보: 객체 내부의 데이터를 보호하여 코드의 안정성과 신뢰성을 높인다.
- 추상화를 통한 복잡성 관리: 복잡한 시스템을 단순화하여 핵심 로직에 집중할 수 있다.
3. 객체 지향 설계의 한계와 문제점
위에서 설명한 바와 같이 OOP는 많은 장점을 가지고 있지만, 특히 성능이 중요한 분야에서는 몇 가지 한계를 드러낸다.
3.1 메모리 비효율성
- 메모리 산재: 객체들이 힙(Heap)에 동적 할당되어 메모리 상에서 연속적이지 않은 위치에 존재한다.
- 캐시 미스(Cache Miss) 증가: 메모리 상에 산발적으로 위치한 데이터를 접근하면 CPU 캐시의 효율이 떨어져 성능 저하를 유발한다.
예시:
// 엔티티 클래스
class Entity {
public:
virtual void update() = 0;
virtual ~Entity() = default;
};
// 다양한 엔티티 객체 생성
std::vector<Entity*> entities;
entities.push_back(new Player());
entities.push_back(new Enemy());
// ...
// 엔티티 업데이트
for (auto& entity : entities) {
entity->update();
}
- 각 엔티티 객체는 힙 메모리에 동적으로 할당되며, 메모리 주소가 연속적이지 않다.
- 루프를 통해 객체를 순회할 때 메모리 접근 패턴이 불규칙하여 캐시 미스가 발생한다.
3.2 동적 바인딩 오버헤드
- 가상 함수 호출: 다형성을 구현하기 위해 가상 함수를 사용하며, 이는 런타임에 실제 함수가 결정되는 동적 바인딩을 필요로 한다.
- 오버헤드 발생: 가상 함수 테이블(V-Table)을 참조하여 함수를 호출하는 과정에서 추가적인 메모리 접근과 연산이 발생한다.
3.3 데이터 접근의 비효율성
- 캡슐화로 인한 제약: 객체 내부의 데이터를 보호하기 위해 private 또는 protected로 선언하여 직접 접근이 불가능하다.
- 함수를 통한 간접 접근: 데이터에 접근하거나 변경하기 위해서는 접근자(getter)나 설정자(setter) 함수를 사용해야 하며, 이는 추가적인 함수 호출 오버헤드를 발생시킨다.
3.4 복잡성 증가
- 과도한 상속 구조: 깊은 상속 계층은 코드를 이해하고 유지보수하는 데 어려움을 준다.
- 디자인 패턴의 남용: 필요 이상으로 복잡한 디자인 패턴을 적용하면 오히려 코드의 가독성과 이해도를 떨어뜨린다.
- 디버깅의 어려움: 다형성과 동적 바인딩으로 인해 버그의 원인을 추적하기 어려울 수 있다.
4. 데이터 중심 설계(DOD)의 등장과 필요성
4.1 DOD의 기본 개념
데이터 중심 설계(Data-Oriented Design, DOD)는 프로그램의 중심을 데이터에 두고, 데이터의 배치와 처리 방식을 최적화하여 성능을 극대화하는 프로그래밍 패러다임이다.
- 데이터 주도 접근: 프로그램의 핵심은 데이터이며, 코드는 데이터를 변환하거나 이동시키는 도구로 본다.
- 메모리 레이아웃 최적화: 데이터가 메모리에 연속적으로 배치되도록 설계하여 CPU 캐시의 효율을 높인다.
- 일괄 처리 방식: 동일한 작업을 여러 데이터에 반복적으로 수행하여 분기(branch)를 최소화하고, 벡터화(vectorization)를 통한 병렬 처리를 용이하게 한다.
[OOP 방식]
+-----------------+ +-----------------+ +-----------------+
| Entity 객체 | | Entity 객체 | | Entity 객체 |
| - position | | - position | | - position |
| - velocity | | - velocity | | - velocity |
| - update() | | - update() | | - update() |
+-----------------+ +-----------------+ +-----------------+
[메모리 상의 배치]
객체1 위치: 메모리 주소 0x1A2B
객체2 위치: 메모리 주소 0x3C4D
객체3 위치: 메모리 주소 0x5E6F
[DOD 방식]
positions: [pos1][pos2][pos3][...]
velocities: [vel1][vel2][vel3][...]
4.2 DOD가 효과적인 분야
DOD가 효과적일 것으로 기대되는 분야는 대개, 하드웨어의 성능을 극한까지 끌어올려 더 빠르고 넓은 대역폭의 연산을 요구하는 분야이다.
- 게임 개발: 수천에서 수백만 개의 엔티티를 실시간으로 업데이트해야 하는 상황에서 성능 향상이 필수적이다.
- 그래픽 렌더링: 대량의 버텍스(vertex)와 픽셀(pixel) 데이터를 처리할 때 캐시 효율이 중요하다.
- 과학 계산 및 시뮬레이션: 방대한 데이터를 고속으로 연산해야 하는 분야에서 유용하다.
- 빅데이터 처리: 대용량 데이터의 분석과 처리에서 메모리 접근 패턴의 최적화가 성능을 좌우한다.
5. DOD의 실제 예제
5.1 DOD의 특징
예제를 탐구해보기에 앞서 DOD가 가진 특징을 다시 한 번 환기해보자.
CPU 캐시와 메모리 접근:
- CPU 캐시는 메모리 접근 속도를 향상시키기 위한 작은 크기의 고속 메모리이다.
- 캐시 히트(Cache Hit): 필요한 데이터가 캐시에 존재하여 빠르게 접근할 수 있는 경우.
- 캐시 미스(Cache Miss): 필요한 데이터가 캐시에 없어서 메인 메모리에서 가져와야 하는 경우.
데이터의 연속성:
- 데이터가 메모리에 연속적으로 배치되면 한 번의 메모리 접근으로 여러 데이터를 캐시에 로드할 수 있다.
- 이를 통해 캐시 히트율을 높이고 성능을 향상시킬 수 있다.
벡터화(Vectorization):
- SIMD(Single Instruction Multiple Data): 하나의 명령어로 여러 데이터를 동시에 처리하는 기술.
- 루프 내의 연산이 단순하고 데이터가 연속적이면 컴파일러가 자동으로 벡터화하여 SIMD 명령어를 사용할 수 있다.
5.2 게임 엔진의 엔티티 업데이트 예제
목표: 다수의 엔티티의 위치를 매 프레임마다 효율적으로 업데이트한다.
5.2.1 OOP 방식의 구현
#include <vector>
// 엔티티 기본 클래스
class Entity {
public:
virtual void update() = 0;
virtual ~Entity() = default;
protected:
float position;
float velocity;
};
// 플레이어 클래스
class Player : public Entity {
public:
void update() override {
position += velocity;
// 추가적인 플레이어 로직...
}
};
// 적 클래스
class Enemy : public Entity {
public:
void update() override {
position += velocity;
// 추가적인 적 로직...
}
};
int main() {
std::vector<Entity*> entities;
// 엔티티 생성 및 추가
for (int i = 0; i < 1000; ++i) {
if (i % 2 == 0)
entities.push_back(new Player());
else
entities.push_back(new Enemy());
}
// 매 프레임마다 업데이트
for (auto& entity : entities) {
entity->update(); // 가상 함수 호출
}
// 메모리 해제
for (auto& entity : entities) {
delete entity;
}
return 0;
}
위의 코드는 객체지향 프로그래밍이 익숙한 사람들에게는 더할나위 없이 편안한 코드일 것이다. 하지만 우리의 CPU는 생각이 좀 다르다.
- 가상 함수 호출로 인한 오버헤드
- 동적 바인딩 비용: entity->update() 호출 시 가상 함수 테이블(vtable)을 참조하여 실제 함수를 결정한다. 이 과정에서 추가적인 메모리 접근과 연산이 필요하며, 이는 CPU 파이프라인의 예측 불가능성을 높여 성능 저하를 유발한다.
- 인라인화 불가: 컴파일러는 가상 함수를 인라인화할 수 없으므로, 함수 호출 오버헤드가 제거되지 않는다.
- 메모리 비연속성으로 인한 캐시 효율 저하
- 힙 메모리의 산재: new 연산자를 통해 동적으로 할당된 객체들은 메모리 상에서 연속적으로 배치되지 않을 수 있다.
- 캐시 미스 증가: 엔티티 객체들이 메모리 전역에 흩어져 있으므로, 루프 내에서 entity->update()를 호출할 때마다 CPU 캐시에 로드되지 않은 메모리 영역에 접근하게 되어 캐시 미스가 발생한다.
- 메모리 사용량 증가
- 추가적인 메타데이터: 가상 함수 테이블 포인터(vptr) 등 객체 지향 특성으로 인해 각 객체의 메모리 크기가 증가한다.
- 메모리 단편화: 동적 메모리 할당으로 인해 메모리 단편화가 발생하여 메모리 사용 효율이 떨어진다.
- 복잡한 상속 구조로 인한 유지보수 어려움
- 상속 계층의 증가: 다양한 엔티티 타입을 지원하기 위해 상속 구조가 깊어지면, 코드의 복잡도가 높아지고 이해하기 어려워진다.
- 의도치 않은 동작: 상속 및 다형성으로 인해 발생하는 예기치 않은 버그를 디버깅하기 어렵다.
5.2.2 DOD 방식
#include <vector>
#include <cstddef>
// 엔티티 데이터 구조체
struct EntityData {
std::vector<float> positions;
std::vector<float> velocities;
};
// 엔티티 업데이트 함수
void updateEntities(EntityData& data) {
size_t count = data.positions.size();
// 루프 벡터화를 위해 단순한 연산 유지
for (size_t i = 0; i < count; ++i) {
data.positions[i] += data.velocities[i];
}
}
int main() {
EntityData entities;
// 데이터 초기화 (예시로 1000개의 엔티티 생성)
for (int i = 0; i < 1000; ++i) {
entities.positions.push_back(static_cast<float>(i));
entities.velocities.push_back(0.5f);
}
// 매 프레임마다 업데이트
updateEntities(entities);
return 0;
}
위의 구현을 살펴보면, 기존의 AoS(Array of Struct)에서 SoA(Struct of Array) 방식으로 변경된 것을 알 수 있다. 이를 통해 우리가 기대해볼 수 있는 장점은 다음과 같다.
- 캐시 효율 향상
- 연속적인 메모리 배치: positions와 velocities 벡터의 데이터는 메모리에 연속적으로 저장된다.
- 캐시 히트율 증가: 연속된 메모리 접근으로 인해 CPU 캐시에 한 번에 여러 데이터가 로드되어 캐시 히트율이 높아진다.
- 공간 지역성(Spatial Locality): 인접한 메모리 주소에 접근하므로 캐시의 효율이 극대화된다.
- 오버헤드 감소
- 가상 함수 제거: 동적 바인딩이 없으므로 함수 호출 오버헤드가 감소한다.
- 인라인화 가능: updateEntities 함수는 컴파일러가 인라인화하여 함수 호출 오버헤드를 제거할 수 있다.
- 컴파일러 최적화 용이
- 루프 벡터화: 단순한 반복문은 컴파일러가 자동으로 SIMD 명령어를 사용하도록 최적화할 수 있다.
- 분기 최소화: 복잡한 조건문이나 가상 함수 호출이 없어 CPU 파이프라인이 효율적으로 동작한다.
- 메모리 사용량 감소
- 필요한 데이터만 저장: 불필요한 메타데이터가 없으므로 메모리 사용량이 감소한다.
- 메모리 단편화 감소: 동적 메모리 할당이 최소화되어 메모리 단편화가 줄어든다.
- 데이터 처리의 단순화
- 일괄 처리 가능: 동일한 연산을 대량의 데이터에 적용하기 쉽다.
- 병렬 처리 용이: 데이터 간의 의존성이 낮아 멀티스레딩이나 GPU를 활용한 병렬 처리가 가능하다.
요약
- 데이터 배치의 최적화는 캐시 효율 향상으로 이어지고, 이는 곧 프로그램 성능의 향상으로 연결된다.
- 가상 함수 제거로 인해 동적 바인딩 오버헤드가 없어지고, 이는 CPU 파이프라인의 효율적 활용을 가능하게 한다.
- 컴파일러 최적화의 용이성은 코드의 단순화에서 비롯되며, 이는 실행 속도 개선을 가져온다.
5.2.3 파티클 시스템 예제
목표: 대량의 파티클을 실시간으로 업데이트하며, 성능을 최적화한다.
OOP 방식:
class Particle {
public:
void update() {
position += velocity;
life -= decay;
}
private:
float position;
float velocity;
float life;
float decay;
};
std::vector<Particle> particles;
// 파티클 초기화
for (int i = 0; i < 100000; ++i) {
particles.emplace_back(/* 초기값 */);
}
// 매 프레임마다 업데이트
for (auto& particle : particles) {
particle.update();
}
그런데 위의 구현에는 아래와 같은 문제가 있다.
- 메모리 비연속성
- 객체의 크기 증가: 각 Particle 객체는 여러 멤버 변수를 포함하므로 크기가 크다.
- 메모리 배치의 비효율성: std::vector<Particle>은 내부적으로 연속된 메모리를 사용하지만, 객체의 크기가 커서 캐시 라인에 더 적은 수의 객체가 들어간다.
- 캐시 미스 증가: 루프 내에서 각 객체의 데이터를 접근할 때 캐시 미스가 발생할 가능성이 높아진다.
- 함수 호출 오버헤드
- 인라인화 제한: 멤버 함수 update()는 인라인화되지 않을 수 있으며, 함수 호출 오버헤드가 발생한다.
- 컴파일러 최적화 어려움
- 복잡한 데이터 구조: 객체 내부의 데이터에 대한 의존성이 높아 컴파일러가 벡터화 최적화를 수행하기 어렵다.
- 메모리 사용량 증가
- 불필요한 데이터 저장: 모든 파티클이 동일한 속성을 가지는 경우에도 각 객체마다 데이터를 저장하여 메모리 낭비가 발생한다.
DOD 방식:
struct ParticleData {
std::vector<float> positions;
std::vector<float> velocities;
std::vector<float> lives;
std::vector<float> decays;
};
void updateParticles(ParticleData& data) {
size_t count = data.positions.size();
// 루프 벡터화를 위해 단순한 연산 유지
for (size_t i = 0; i < count; ++i) {
data.positions[i] += data.velocities[i];
data.lives[i] -= data.decays[i];
}
}
ParticleData particles;
// 파티클 초기화
for (int i = 0; i < 100000; ++i) {
particles.positions.push_back(/* 초기값 */);
particles.velocities.push_back(/* 초기값 */);
particles.lives.push_back(/* 초기값 */);
particles.decays.push_back(/* 초기값 */);
}
// 매 프레임마다 업데이트
updateParticles(particles);
이렇게 구현을 변경했을 때 우리가 얻을 수 있을 것으로 기대되는 장점은 다음과 같다.
- 메모리 접근 효율 극대화
- 데이터의 연속성: 각 속성별로 데이터를 연속된 메모리에 저장하여 캐시 효율을 높인다.
- 캐시 히트율 향상: 루프 내에서 인접한 메모리 주소를 순차적으로 접근하므로 캐시 미스가 감소한다.
- 벡터화 최적화 가능
- 단순한 연산 구조: 각 속성별로 동일한 연산을 수행하므로 SIMD 명령어를 사용한 벡터화가 가능하다.
- 컴파일러 최적화 지원: 컴파일러가 자동으로 루프를 벡터화하여 성능을 향상시킨다.
- 메모리 사용량 최적화
- 필요한 데이터만 저장: 파티클별로 필요한 속성만 저장하여 메모리 낭비를 줄인다.
- 메모리 단편화 최소화: 동적 할당을 최소화하여 메모리 관리 효율을 높인다.
- 병렬 처리 용이
- 데이터 독립성: 파티클 간의 데이터 의존성이 없으므로 멀티스레딩이나 GPU를 활용한 병렬 처리가 가능하다.
- 스케일링 가능성: 데이터 양이 증가해도 병렬화를 통해 성능 저하를 최소화할 수 있다.
- 함수 호출 오버헤드 감소
- 단일 함수 사용: updateParticles 함수 하나로 모든 파티클을 업데이트하여 함수 호출 오버헤드를 줄인다.
- 인라인화 가능성: 단순한 함수 구조로 인해 컴파일러가 인라인화하여 성능을 개선할 수 있다.
6. 복습
이번 글에서 특히 중요한 용어들을 다시 한 번 짚고 넘어가자.
- CPU 캐시(Cache)
- 정의: CPU와 메인 메모리 사이에 위치한 고속 메모리로, 자주 사용되는 데이터를 저장하여 메모리 접근 속도를 향상시킨다.
- 계층 구조: 일반적으로 L1, L2, L3 캐시로 구성되며, L1이 가장 빠르고 용량이 적다.
- 중요성: 프로그램의 성능은 캐시 히트율에 크게 영향을 받으며, 캐시 효율을 높이는 것이 성능 최적화의 핵심이다.
- 캐시 미스(Cache Miss)
- 정의: CPU가 필요한 데이터를 캐시에서 찾지 못하고 메인 메모리에서 가져와야 하는 상황.
- 종류:
- Cold Miss: 처음 접근하는 데이터로 인해 발생.
- Conflict Miss: 캐시의 한정된 크기로 인해 발생하는 충돌로 인한 미스.
- Capacity Miss: 캐시 용량이 부족하여 발생.
- 영향: 캐시 미스가 발생하면 메모리 접근 지연이 생겨 프로그램의 성능이 저하된다.
- 동적 바인딩(Dynamic Binding)
- 정의: 프로그램 실행 중에 함수 호출이 결정되는 방식으로, 가상 함수를 통해 구현된다.
- 가상 함수 테이블(V-Table): 클래스의 가상 함수 주소를 저장하는 테이블로, 동적 바인딩 시 사용된다.
- 오버헤드: 동적 바인딩은 추가적인 메모리 접근과 연산을 필요로 하여 성능에 부정적인 영향을 줄 수 있다.
- 벡터화(Vectorization)
- 정의: 하나의 명령어로 여러 데이터를 동시에 처리하는 기술로, SIMD 명령어를 활용한다.
- SIMD(Single Instruction Multiple Data): 동일한 연산을 여러 데이터에 동시에 수행하는 병렬 처리 방식.
- 장점: 벡터화를 통해 연산 속도를 크게 향상시킬 수 있다.
- 조건: 데이터가 연속적이고, 연산이 단순하며, 데이터 간 의존성이 없어야 한다.
- 메모리 단편화(Memory Fragmentation)
- 정의: 메모리 할당과 해제가 반복되면서 사용되지 않는 작은 메모리 조각들이 생겨 전체 메모리 사용 효율이 떨어지는 현상.
- 종류:
- 외부 단편화: 사용되지 않는 메모리 블록들이 흩어져 있어 큰 메모리 요청을 처리할 수 없는 경우.
- 내부 단편화: 할당된 메모리 블록 내에 사용되지 않는 공간이 남아 있는 경우.
- 해결 방법: 메모리 풀(Memory Pool) 사용, 메모리 할당 전략 개선 등이 있다.
- 공간 지역성(Spatial Locality)
- 정의: 한 번 접근한 메모리 주소 근처의 주소를 곧바로 다시 접근하는 경향.
- 중요성: 공간 지역성을 높이면 캐시 효율이 향상되어 프로그램 성능이 개선된다.
- 관련 개념: 시간 지역성(Temporal Locality)은 동일한 메모리 주소를 짧은 시간 내에 반복해서 접근하는 경향을 말한다.
- 인라인화(Inlining)
- 정의: 함수 호출을 함수의 본체로 대체하여 함수 호출 오버헤드를 제거하는 컴파일러 최적화 기법.
- 조건: 함수가 충분히 작고, 컴파일러가 인라인화가 성능에 도움이 된다고 판단할 때.
- 제한 사항: 가상 함수나 재귀 함수는 인라인화가 불가능하거나 제한적이다.
이러한 용어들은 이번 글에서 데이터 중심 설계(DOD)의 중요성을 이해하는 데 핵심적인 개념들이다. 특히 CPU 캐시의 작동 원리와 캐시 효율이 프로그램 성능에 미치는 영향을 잘 이해해야 DOD의 장점을 최대한 활용할 수 있다.
7. 앞으로의 방향
이번 글을 통해 DOD가 가질 수 있는 기술적인 이점을 조명해보았다. 앞으로 이 시리즈를 통해 기존에 흔히 알려진 OOP 방식의 프로그래밍이 자칫 재앙적인 성능 하락을 일으킬 수 있는 부분들을 찾아 자세히 살펴보고, DOD관점에서 재설계를 해보며 다양한 실험들을 해보고자 한다. 이 과정을 통해 한 사람의 프로그래머로써 우리가 개발한 프로그램이 실행되는 하드웨어와 보다 더 친해지고, 또한 프로그램의 성능을 한계까지 끌어올리는 짜릿한 엔지니어링의 시간을 가져볼 것이다.
'개발 > C++' 카테고리의 다른 글
CppCon 2014: "Data-Oriented Design and C++" (2) | 2024.10.15 |
---|