본문 바로가기
책/Effective C#

Effective C# - Item 15 불필요한 객체를 만들지 말라

by 코딩하는 돼징 2023. 12. 3.
반응형

GC( Garbage Collection )는 사용자를 대신하여 사용하지 않는 객체를 효율적인 방식으로 제거한다. 하지만 이러한 작업이 아무리 효율적이라 하더라도 힙에서 새로운 객체를 생성하고 삭제하는 작업은 그러한 일을 전혀 하지 않는 것에 비한다면 상대적으로 많은 프로세서 시간을 사용하므로 성능 문제를 일으킬 수 있다.

 

따라서 GC가 과도하게 동작하지 않도록 주의 해야한다. 기술적으로 몇 가지만 유념하면 GC의 작업을 현저히 줄일 수 있다.


GC 작업을 줄일 수 있는 방법

01 지역변수를 멤버 변수로 변경

모든 참조 타입의 객체는 지역변수라 하더라도 동적으로 메모리를 할당한다. 이렇게 할당된 객체는 이 객체를 참조하는 상위 객체가 삭제되면 가비지가 된다. 지역변수의 경우 그 변수를 선언한 메서드를 벗어나는 순간 가비자기 되어 더 이상 살아 있는 객체로 간주 되지 않는다.

예시

1) Font객체가 지역변수인 경우

Font객체를 Paint이벤트 핸들러 내에서 생성하고 있다. 이는 Paint이벤트가 호출될때마다 동일한 Font객체가 반복적으로 생성되고 해제된다. 이로인해 GC가 더 자주 수행되고 메모리 할당이 늘어 난다.

protected override void OnPaint(PaintEventArgs e)
{
    // 나쁜 예. Paint 이벤트가 발생할 때마다 동일한 폰트를 생성한다.
    using(Font MyFont = new Font("Arial",10.0f))
    {
        e.Graphics.DrawString(DataTime.Now.ToString(),
            MyFont,Burshes.Black,new PointF(0,0));
    }
    
    base.OnPaint(e);
}

 


2) Font객체가 멤버변수인 경우

Font 객체를 한 번만 생성한 후 이를 재사용하도록 한다.

protected override void OnPaint(PaintEventArgs e)
{
    e.Graphics.DrawString(DataTime.Now.ToString(),
            MyFont,Burshes.Black,new PointF(0,0));
    
    base.OnPaint(e);
}

이처럼 코드를 수정하면 Paint 이벤트가 발생할 때마다 새로운 가바지가 생성되지 않으므로 GC가 해야하는 일의 양도 줄게 된다. 

 

자주 호출되는 메서드 내에서 참조 타입(값 타입은 문제가 되지 않음)의 객체를 매번 생성하는 경우라면 지역변수를 멤버 변수로 변경하는 것이 좋다. 하지만 호출 빈도가 높지 않은 경우라면 굳이 변경할  필요가 없다. 지역 변수를 모조리 멤버 변수로 변경하여 동일한 객체를 생성하는 것을 완전히 피하라는 이야기는 아니다.


02 종속성 타입(Dependency Injection) - 정적 멤버 변수

객체간의 의존성을 외부에서 주입하여 객체 간 결합도를 낮추고 유연성을 높인다. 이는 객체가 직접 자신이 필요로 하는 리소스를 생성하지 않고 외부에서 주입받아 사용하는 방시이다. 이를 사용하면 리소스를 생성하거나 관리하지 않아도 된다. 

 

Unity - 의존성 주입(Dependency Injection)

의존성 타입(Dependency Injection) 정적 멤버 변수객체간의 의존성을 외부에서 주입하여 객체 간 결합도를 낮추고 유연성을 높인다. 이는 객체가 직접 자신이 필요로 하는 리소스를 생성하지 않고 외

code-piggy.tistory.com


자주 사용되는 참조타입의 인스턴스를 정적 멤버 변수로 선언한다.

 

private static Brush blackBrush;

public static Brush Black
{
    get
    {
        if(blackBrush == null)
            blackBrush = new SolidBrush(Color.Black)
        return blackBrush;
    }
}

Brushes 클래스는 이렇게 생성된 검정 브러시를 저장해두고 동일한 요청이 있을 때마다 이 객체를 준다. 검정 브러시 하나를 영원히 재사용하게 된다. 

그러므로 경우에 따라서는 생성된 객체가 메모리상에 필요 이상으로 오랫동안 남아있을 수도 있다. 또한 Dispose()메서드를 호출해야할 시점을 결정할 수 없기 때문에 비관리 리소르를 삭제할 수 없다는 것도 큰 단점이다.


03 변경 불가능한(Immutable)타입과 관련된 부분

변경 불가능한 타입의 대표적인 예로 System.String이 있다. string객체가 생성되면 이 객체가 가지고 있는 문자열의 내용은 수정이 불가능하다. 우리가 코딩을할때 문자열을 수정한다고 생각할 수 있지만 실제로 문자열을 변경하는 것이 아니라 새로운 문자열을 생성하고 이를 반환하는 것이다.

public static void Main(string[] args)
{
    string msg = "Hello, ";
    msg += thisUser.Name;
    msg += ". Today is ";
    msg += System.DataTime.Now.To.Strin();
}

만약 앞에서와 같이 코드를 작성했다면 실제로 매우 비효율적인 작업이 이루어진다.

string msg = "Hello, ";
// 설명을 위한 코드이며, 유효한 코드는 아니다.
string tmp1 = new String(msg + thisUser.Name);
msg = tmp1; // "Hello, "는 가비지가 된다.
string tmp2 = new String(msg + ".Today is");
msg = tmp2; // "Hello, ", <user>" 는 가비지가 된다.
string tmp3 = new String(msg + DataTime.Now.ToString());
msg tmp3; // "Hello, ", <user>, .Today is" 는 가비지가 된다.

 

tmp1,tmp2,tmp3와 최초 msg("Hello")는 모두 가비지가 된다. string 클래스 내외 +=연산자는 기존 문자열에 새로운 문자열을 더하는 것이 아니라 완전히 새로운 string객체를 생성하여 반환횐다.


아래와 같이 작성하는 것을 추천한다.

1) 문자열 보간

string msg = string.Fomrat("Hello,{0}.Today is {1}", thisUser.Name, DataTime.Now.ToString());

2) StringBuilder

StringBuilder msg = new StringBuilder("Hello, ");

msg.Append(thisUser.Name);
msg.Append(". Today is");
msg.Append(DataTime.Now.ToString());

string finalMsg = msg.ToString();

결론

GC는 응용프로그램이 사용하는 메모리를 효율적으로 관리한다. 그러나 힙에서 객체를 생성하고 삭제하려면 여전히 시간이 필요하다. 객체를 과도하게 생성하는 것을 피하고 불필요한 객체를 생성하지 말라

 

 

 

 

 

본 게시글은 Effective C#을 읽고 정리하였습니다.

 

 

 

반응형

댓글