1. 경량 패턴이란?
- 재사용 가능한 객체 인스턴스를 공유시켜 메모리 사용량을 최소화하는 구조 패턴.
- 한 개의 고유 상태를 다른 객체들에서 공유하게 만들어 메모리 사용량을 줄이는 패턴
- 내용이 같은 객체가 이미 있으면 새로 객체를 만들지 않고, 내용이 같은 기존 객체를 공유한다.
- 동일하거나 유사한 객체들 사이에 가능한 많은 데이터를 서로 공유하며 사용하도록 메모리 사용량을 최소화하는 패턴.
- 반복된 데이터에 대해 하드웨어의 부담을 줄여주는 패턴
2. 경량 패턴을 사용하는 경우
- 메모리를 최소한으로 사용해야하는 경우
- 공통적인 인스턴스를 많이 생성하는 로직이 포함된 경우
- 생성된 객체가 오래도록 메모리에 상주하며 사용되는 횟수가 많은 경우
- 어플리케이션에 의해 생성되는 객체의 수가 많아 저장 비용이 높아지는 경우
- 게임에서 숲이나 유닛, 지형, 자잘한 환경 오브젝트 등 서로 비슷한 요소를 가지면서 많은 객체를 필요로 할 때 리소스의 최적화를 위해 사용
3. 경량 패턴의 장점
- 공통적인 속성을 갖는 객체 생성시 메모리 양을 줄일 수 있음.
- 프로그램 속도를 개선할 수 있음.
4. 경량 패턴의 단점
- 코드의 복잡성 증가
- 데이터를 서로 공유하며 사용하도록 구현되므로, 해당 패턴에 포함된 객체들 중에서 일부만 다른 식으로 동작하도록 구현하는 것에는 무리가 있다.
5. 코드 구현(C#)
1) 패턴 적용 전 소스코드
using System;
namespace StudyCSharp
{
class Memory
{
public static long size = 0; // 메모리 사용량
public static void print()
{
Console.WriteLine("총 메모리 사용량 : " + Memory.size + "MB");
}
}
class Tree
{
long objSize = 100; // 메모리 100MB 사용
string _type; // 나무 종류
Object _mesh; // 메쉬
Object _texture; // 나무 껍질 + 잎사귀 텍스쳐
// 위치 변수
public double _posX;
public double _posY;
public Tree(string type, Object mesh, Object texture, double posX, double posY)
{
this._type = type;
this._mesh = mesh;
this._texture = texture;
this._posX = posX;
this._posY = posY;
// 객체를 생성하였으므로 메모리 사용량 증가
Memory.size += this.objSize;
}
}
class Terrain
{
// 지형 타일의 크기
public const int CANVAS_SIZE = 1000;
// 렌더링 함수
public void rendering(string type, Object mesh, Object texture, double posX, double posY)
{
// 나무 객체 생성
Tree tree = new Tree(type, mesh, texture, posX, posY);
Console.WriteLine("x: {0}\ty: {1}\t위치에 {2} 나무 생성 완료", tree._posX, tree._posY, type);
}
}
class Program
{
static void Main(string[] args)
{
// 지형 생성
Terrain terrain = new Terrain();
// 랜덤수 생성
Random random = new Random();
// 지형에 Oak 나무 5그루 생성
for (int i = 0; i< 5; i++)
{
terrain.rendering("Oak", new object(), new object(),
(int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0, (int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0);
}
Console.WriteLine();
// 지형에 Acacia 나무 5그루 생성
for (int i = 0; i < 5; i++)
{
terrain.rendering("Acacia", new object(), new object(),
(int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0, (int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0);
}
Console.WriteLine();
// 지형에 Jungle 나무 5그루 생성
for (int i = 0; i < 5; i++)
{
terrain.rendering("Jungle", new object(), new object(),
(int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0, (int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0);
}
Console.WriteLine();
// 메모리 사용량 출력
Memory.print();
Console.WriteLine();
}
}
}
- 실행결과 : 100MB의 나무 15그루를 생성했으므로 15x100MB = 1500MB의 메모리가 사용된다.
2) 패턴 적용 후 소스코드(c#)
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
class Memory
{
public static long size = 0; // 메모리 사용량
public static void print()
{
Console.WriteLine("총 메모리 사용량 : " + size + "MB");
}
}
// UnsharedConcreteFlyweight : 공유되지 않는 속성값들
class Tree
{
// 나무의 위치값과 나무모델 참조 객체의 크기를 합친 메모리의 크기
long objSize = 10; // 10MB
// 위치 변수
public double _posX;
public double _posY;
// 나무 모델
TreeModel _model;
public Tree(TreeModel model, double posX, double posY)
{
this._model = model;
this._posX = posX;
this._posY = posY;
// 객체를 생성하였으므로 메모리 사용량 증가
Memory.size += this.objSize;
}
}
// ConcreteFlyweight : 공유되는 속성값들
// 플라이웨이트 객체는 불변성을 가져야하기 때문에 sealed 클래스를 사용했음.
sealed class TreeModel
{
// 위치값을 제외한 나머지 메모리 크기
long objSize = 90; // 90MB
string _type; // 나무 종류
object _mesh; // 메쉬
object _texture; // 나무 껍질 + 잎사귀 텍스쳐
public TreeModel(string type, object mesh, object texture)
{
this._type = type;
this._mesh = mesh;
this._texture = texture;
// 메모리 사용량 증가
Memory.size += this.objSize;
}
}
// FlyweightFactory : 플라이웨이트 팩토리
// 1. Flyweight Pool : HashMap 컬렉션을 통해 키(key)와 나무 모델 객체를 저장하는 캐시 저장소 역할
// 2. getInstance 메서드 : Pool에 가져오고자 하는 객체가 있는지를 검사하여 있으면 그대로 반환, 없으면 새로 생성
class TreeModelFactory
{
// Flyweight Pool - TreeModel 객체들을 Map으로 등록하여 캐싱
private static readonly Dictionary<string, TreeModel> cache = new Dictionary<string, TreeModel>();
public static TreeModel GetInstance(string key)
{
// 이미 캐시가 되어있는 경우
if (cache.ContainsKey(key))
{
Console.Write("재사용 객체 사용\t");
return cache[key];
}
else // 캐시 되어있지 않은 경우 -> 나무 모델 객체를 새로 생성 후 반환
{
TreeModel model = new TreeModel(key, new object(), new object());
Console.Write("-객체 새로 생성-\t");
// 캐시에 적재
cache.Add(key, model);
// 객체 반환
return model;
}
}
}
class Terrain
{
// 지형 타일의 크기
public const int CANVAS_SIZE = 1000;
// 렌더링 함수
public void rendering(string type, double posX, double posY)
{
// 1. 캐시 되어 있는 나무 모델 객체 가져오기
TreeModel model = TreeModelFactory.GetInstance(type);
// 2. 재사용한 나무 모델 객체와 변하는 속성인 위치값을 이용해 나무 객체 생성
Tree tree = new Tree(model, posX, posY);
Console.WriteLine("x: {0}\ty: {1}\t위치에 {2} 나무 생성 완료", tree._posX, tree._posY, type);
}
}
class Program
{
static void Main(string[] args)
{
// 지형 생성
Terrain terrain = new Terrain();
// 랜덤수 생성
Random random = new Random();
// 지형에 Oak 나무 5그루 생성
for (int i = 0; i< 5; i++)
{
terrain.rendering("Oak",
(int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0, (int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0);
}
Console.WriteLine();
// 지형에 Acacia 나무 5그루 생성
for (int i = 0; i < 5; i++)
{
terrain.rendering("Acacia",
(int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0, (int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0);
}
Console.WriteLine();
// 지형에 Jungle 나무 5그루 생성
for (int i = 0; i < 5; i++)
{
terrain.rendering("Jungle",
(int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0, (int)(random.NextDouble() * Terrain.CANVAS_SIZE) / 100.0);
}
Console.WriteLine();
// 메모리 사용량 출력
Memory.print();
Console.WriteLine();
}
}
}
- 실행 결과 : 새로 생성시 100MB, 기존 객체 재사용시 10MB의 메모리가 사용되므로 3x100MB+12x10MB = 420MB의 메모리가 사용된다.
<참고 사이트>