KimMK

경량(Flyweight) 패턴 본문

C++/디자인 패턴

경량(Flyweight) 패턴

KimMK 2023. 3. 11. 18:50

디자인 패턴 중 구조 패턴인 경량 패턴은 객체를 공유해 메모리 사용량을 줄이는 것을 목적으로 한다.

즉, 한개의 고유 상태를 다른 객체들에서 공유하게 만들어 메모리 사용량을 줄이는 것이다.

경량 패턴은 최적화와 밀접한 관계가 있으며 공장에서 같은 제품을 찍어내며 생산하듯이 대량의 데이터를 찍어내기 위해 사용한다.

  • 내용이 같은 객체가 있으면 새로 객체를 생성하지 않고 기존의 객체를 공유한다

 

예를 들어, 게임 엔진인 Unreal에서 폴리지와 같은 툴을 사용할 때 풀과 잔디, 조약돌과 같은 애셋을 이용해 폴리지를 적용하려고 할 때 애셋을 하나하나 mesh, texture와 같은 정보를 렌더링하게 되면 엄청난 오버헤드를 초래하게 된다.

그래서 언리얼 엔진의 애셋은 기본적으로 경량 패턴이 적용되는데, 

경량 패턴은 위와 같이 같은 메시로 숲을 구성한다고 할 때, 메시가 가지고 있는 고유 정보인 메시, 나무 껍질, 입사귀와 같은 Texture들을 여러번 메모리에 올릴 필요가 없기 때문에 객체 하나를 만들어 각 인스턴스들이 이를 공유하며 참조해 메모리 사용량을 줄인다.

생성된 객체를 공유하는 것이라 C++ 포인터 개념과도 같다.

 

 

간단한 예시를 들어 구현을 해보면,

게임에서 여러 개의 적 케릭터가 생성되어야 하는 상황을 가정했을 때, 적 케릭터마다 고유한 모습을 갖게 되므로 각각의 케릭터를 객체로 만들면 메모리 사용량이 증가한다. 그래서  외형이 일치하는 케릭터들끼리 객체로 만들지 않고 하나의 객체를 공유해 사용할 수 있다. 이렇게 하면 메모리 사용량을 줄일 수 있고 성능 향상에도 기여할 수 있다.

class Enemy {
public:
    virtual void draw() = 0;
};

적 케릭터 클래스를 정의하고 고유한 모습을 가지고 있다고 생각

 

class SharedEnemy : public Enemy {
public:
    SharedEnemy(std::string appearance) : appearance(appearance) {}

    void draw() {
        std::cout << "Drawing enemy with appearance: " << appearance << std::endl;
    }

private:
    std::string appearance;
};

공유 가능한 케릭터를 정의한다. 

 

class EnemyFactory {
public:
    Enemy* getEnemy(std::string appearance) {
        if (enemyMap.find(appearance) == enemyMap.end()) {
            // 캐릭터가 생성되지 않은 경우
            enemyMap[appearance] = new SharedEnemy(appearance);
        }
        return enemyMap[appearance];
    }

private:
    std::map<std::string, Enemy*> enemyMap;
};

케릭터를 생성하고 공유하는 클래스를 정의한다. 이 클래스에서 생성된 케릭터를 저장하고 필요한 경우 이를 공유해서 사용하도록 한다.

 

int main() {
    // EnemyFactory 객체 생성
    EnemyFactory* factory = new EnemyFactory();

    // 적 캐릭터 생성
    Enemy* enemy1 = factory->getEnemy("Green Goblin");
    Enemy* enemy2 = factory->getEnemy("Red Goblin");
    Enemy* enemy3 = factory->getEnemy("Green Goblin");

    // 각 캐릭터 그리기
    enemy1->draw();
    enemy2->draw();
    enemy3->draw();

    // 객체 소멸
    delete enemy1;
    delete enemy2;
    delete enemy3;
    delete factory;

    return 0;
}

EnemyFactory 객체를 생성해서 적 케릭터가 이를 공유해서 케릭터를 생성하도록 한다.

그리고 적 케릭터의 각자 고유한 값은 매개변수로 넘겨 차별화를 둔다.

 

 

경량 패턴인스턴싱과도 매우 밀접한 연관이 있다.

*인스턴싱: 동일한 객체를 한 화면에 여러 개 렌더링 시킬 경우 한 번의 DP call로 처리하는 방식을 의미한다.

DirectX에서 하나의 객체를 화면에 렌더링할 때 그래픽스 파이프라인을 거치게 되는데 ([축약] IA -> VS -> RS -> OM)

1000개의 객체를 그려야 한다면 1000번 DP call 과정을 거치며 상당한 자원 소모를 발생시키게 된다.

 

같은 종류의 메시라면 한 번에 모아서 렌더링을 수행함으로써 렌더링 오버헤드. 즉, Draw Call을 줄이는 것이 인스턴싱이다.

각 모델별로 Draw를 발생시키는 것이 아닌 모델의 정보를 모아 한번에 렌더링을 수행시켜 구현할 수 있다.

'C++ > 디자인 패턴' 카테고리의 다른 글

관찰자(Observer) 패턴  (0) 2023.03.14
싱글턴(Singleton) 패턴  (0) 2023.03.12
디자인 패턴  (0) 2023.03.11
명령(Command) 패턴  (0) 2023.03.11
팩토리 패턴  (0) 2023.03.10