Unity 개발일지

[C#] Garbage Collector(가비지컬렉터) 실습하기 본문

C#

[C#] Garbage Collector(가비지컬렉터) 실습하기

아머르 2024. 7. 8. 14:26

[확인 문제]

 

1. 위의 코드가 문제가 되는 이유를 메모리 관점에서 설명해주세요.

더보기

위 코드에서 LogMessages는 문자열 연결 연산('+')을 통해 로그 메시지르 추가한다.

String은 불변 객체이므로, 매번 새로운 문자열 객체가 생성되고, 이전 문자열이 가비지 컬렉션 대상이 된다.

10,000번의 반복문이 실행되면서 많은 양의 메모리 할당과 해제가 반복적으로 발생하여 성능 저하와 메모리 낭비가 발생한다.

 

2. 아래와 같이 string이 아닌 StringBuilder가 권장되는 이유는 무엇일까요?

더보기

StringBuilder는 가변 객체로, 내부 버퍼를 사용하여 문자열을 수정한다.

문자열 연결 시 새로운 객체를 생성하지 않고, 기존 버퍼를 재사용하므로 메모리 사용이 효율적이고, 성능이 향상된다.

 

 

[설명 문제]

 

1. 가비지 컬렉터란 무엇인가요?

더보기

참조타입(reference type) 변수들은 스택 메모리에 주소 값을 담아두고, 힙 메모리에 접근하는 방식으로 저장되어 있는데, 함수가 종료되고 지역 변수들이 제거되면서 스택 메모리에서 힙 메모리를 가리키고 있는 일부 주소값들이 pop되면서, 해당 주소값에 있는 힙 메모리의 데이터에는 더이상 접근할 수 없게 된다. 이렇게 참조할 수 없게 된 객체를 garbage라고 부른다. 이 garbage같은 경우 C#에서 CLR(Common Language Runtime)이 자동적으로 메모리를 관리하는데, 그 기능 중 하나가 자동으로 메모리를 관리해주는 garbage collection이다.

 

가비지 컬렉터는 메모리 관리 시스템으로, 프로그램에서 더 이상 사용되지 않는 객체를 자동으로 해제하여 메모리 누수를 방지하고, 메모리 관리의 부담을 줄여준다.

 

2. 가비지 컬렉터의 장점과 단점에 대해 설명해주세요.

더보기

장점 : 자동 메모리 관리, 프로그래머의 부담 감소, 메모리 누수 방지

단점 : 예측할 수 없는 시점에 작동하여 성능 저하 발생 가능, 실시간 성능 요구가 높은 응용 프로그램에서는 문제 발생 가능

 

3. 가비지 컬렉터의 세대 개념에 대해 설명해주세요.

더보기

Generation (세대)는 몇 번의 가비지 컬렉션을 거쳤는지 나타낸다. Garabage Collection이 일어나고 stack 메모리에서 아직 참조되고 있는 object들에 대해서는 세대 수를 증가시킨다. 여러번 거쳤을 때 남아있다면 프로그램에서 계속해서 사용되고 있는 것이라 볼 수 있기 때문에 이 object들은 매번 가비지 컬렉터가 확인할 필요가 없다는 것을 의미한다.

 

4. 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요?

더보기

박싱 : 값 형식을 참조 형식으로 변환할 때 성능 비용이 발생한다.

언박싱 : 참조 형식을 값 형식으로 변환할 때 성능 비용이 발생하고, 캐스트 예외가 발생할 수 있다.

 

5. 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요?

더보기

객체를 재사용하여 메모리 할당 및 해제 비용을 줄일 수 있다. 이는 메모리 사용을 최적화하고, 가비지 컬렉션의 빈도를 줄여 성능을 향상시키는 데 도움이 된다.

 

 

[실습문제]

오브젝트 풀을 이용하여 적을 생성하는 EnemyPool을 구현해봅시다.

  • GetEnemy 메서드에서 재사용 가능한 Enemy 객체를 가져오고 초기화하는 코드를 작성하세요.
  • ReleaseEnemy 메서드에서 사용한 Enemy 객체를 반환하고, 재사용 리스트에 추가하는 코드를 작성하세요.

더보기
using System;
using System.Collections.Generic;

public class Enemy
{
    public int HP { get; set; }
    public int Attack { get; set; }

    public Enemy(int hp, int attack)
    {
        HP = hp;
        Attack = attack;
    }

    public void Reset(int hp, int attack)
    {
        HP = hp;
        Attack = attack;
    }
}

public class EnemyPool
{
    private List<Enemy> availableEnemies = new List<Enemy>();
    private List<Enemy> usedEnemies = new List<Enemy>();

    public Enemy GetEnemy(int hp, int attack)
    {
        Enemy enemy;
        if (availableEnemies.Count > 0)
        {
            // availableEnemies 리스트에서 객체 하나를 가져온다.
            enemy = availableEnemies[0];
            // 리스트에서 가져온 객체를 제거한다.
            availableEnemies.RemoveAt(0);
            // Reset메서드를 호출하여 적 객체의 hp와 attack값을 재설정한다.
            enemy.Reset(hp, attack);
        }
        else
        {
            enemy = new Enemy(hp, attack);
        }
        usedEnemies.Add(enemy);
        return enemy;
    }

    public void ReleaseEnemy(Enemy enemy)
    {
       // 적 객체를 usedEnemies 리스트에서 제거한다.
       usedEnemies.Remove(enemy);
       // 적 객체를 availableEnemies 리스트에 추가한다.
       availableEnemies.Add(enemy);
    }
}

public class Program
{
    public static void Main()
    {
        EnemyPool enemyPool = new EnemyPool();
        List<Enemy> currentEnemies = new List<Enemy>();

        long beforeGcCount = GC.CollectionCount(0);

        for (int frame = 0; frame < 10000; frame++)
        {
            // 일정 프레임마다 새로운 적 객체 생성
            if (frame % 10 == 0) // 10프레임마다 적 생성
            {
                Enemy newEnemy = enemyPool.GetEnemy(100, 10);
                currentEnemies.Add(newEnemy);
            }

            // 일정 조건에서 적 객체 반환
            if (frame % 20 == 0 && currentEnemies.Count > 0) // 20프레임마다 적 반환
            {
                Enemy oldEnemy = currentEnemies[0];
                currentEnemies.RemoveAt(0);
                enemyPool.ReleaseEnemy(oldEnemy);
            }
        }

        long afterGcCount = GC.CollectionCount(0);

        Console.WriteLine("Total enemies created: " + (enemyPool.usedEnemies.Count + enemyPool.availableEnemies.Count));
        Console.WriteLine("GC collection count before: " + beforeGcCount);
        Console.WriteLine("GC collection count after: " + afterGcCount);
        Console.WriteLine("GC collections occurred: " + (afterGcCount - beforeGcCount));
    }
}

 

 

GetEnemy 메서드 설명

 

지정된 hp와 attack 값을 가진 Enemy 객체를 반환하거나 생성한다.

 

1. 사용 가능한 객체가 있을 때는 availableEnemies 리스트에서 첫 번째 적 객체를 가져온다.

2. 이 객체를 availableEnemies 리스트에서 제거한다.

3. enemy.Reset(hp, attack) 메서드를 호출하여 적 객체의 hp와 attack 값을 재설정한다.

4. 사용 가능한 객체가 없을 때에는 새로운 Enemy 객체를 생성한다.

5. 위에서 생성한 적 객체를 usedEnemies 리스트에 추가한다.

 

ReleaseEnemy 메서드 설명

 

1. 적 객체를 usedEnemies 리스트에서 제거한다.

2. 적 객체를 availableEnemies 리스트에 추가한다.

 

<ObjectPooling >

사용한 Enemy 객체를 재사용 할 수 있도록 풀에 반환한다.

객체 풀링(Object Pooling) : 객체를 재사용하여 빈번한 메모리 할당 및 해제에 따른 성능 저하를 방지한다.

메모리 관리 : Enemy 객체를 재활용함으로써 새로운 객체 생성을 최소화하여 메모리 사용 효율성을 높이고, 게임 성능을 향상시킨다.

 

 

반응형