불필요한 객체를 만들지 말라

 

이전에 포스팅한 가비지 컬렉터에 대해 로드를 줄이는 방법을 포스팅하겠다.

 

1. 자주 호출되는 함수에 생성하는 변수는 멤버 변수를 고려해보자

 

override void OnPaint(EvengArgs e)
{
    using(Font MyFont = new Font("Blue", 10.0f))
    {
    	e.Graphics.DrawString(MyFont...);
    }
    
    base.OnPaint(e);
}

 

GUI 프로그램을 해본 개발자들은 OnPaint 함수에 대해 알 것이다.

정말 자주 호출되는 함수이다. (마우스가 해당 UI 컨트롤 위에 한 픽셀 움직이기만 해도 호출된다.)

 

그럼 당연히 MyFont라는 객체는 OnPaint함수가 호출되는 만큼 지역적으로 할당했다가 가비지 객체로 변한다.

이 작업이 100번, 500번, 10000번 호출되면 가비지 객체가 늘어나고 결국 가비지 컬렉터가 3초만 기다려도 미치는 사용자들에게 피해를 줄 것이다.

해결 방법은 간단하다. MyFont를 멤버 변수로 쓱 올려주면 끝이다.

Font _myFont = new Font("Blue", 10.0f);

override void OnPaint(EvengArgs e)
{
    e.Graphics.DrawString(_myFont...); 
    base.OnPaint(e);
}

자 해결되었다. 하지만 문제가 있다. Font 타입은 IDisposable 인터페이스를 구현한 타입의 객체는 멤버변수로 변하면

우리 개발자들은 야근을 피하기 위해 Dispose패턴을 사용하여 Font객체를 제거해줘야 한다.  Dispose패턴은 ITEM 17에 포스팅할 것이니 알고 싶으면 ITEM17로 가길 바란다.

 

2. 지연 평가(Lazy evaluation) 알고리즘을 고려해보자

 

지연 평가 알고리즘 원리는 간단하다. 

호출 직전까지 할당을 지연시키는 것이다.

실제 .NET Framework 설계자는 지연 평가 알고리즘을 이용해 정적 속성인 Brush 객체를 설계했다.

 

    1. 호출 전까지 null형태로 메모리를 사용하지 않는다.

    2. 호출 시 null 체크 후 정적 속성으로 메모리를 잡는다.

    3. 이후 호출에는 같은 객체를 사용하여 추가 로드를 잡지 않는다.

 

private static Brush blackBrush;

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

 

위의 두 방법은 단점도 고려해봐야 한다.

    1. 경우에 따라 생성된 객체가 메모리상에 필요 이상으로 오랫동안 남아 있을 수 있다.

    2. Dispose() 메서드를 호출해야 할 시점을 결정할 수 없기 때문에 비관리 리소스를 삭제할 수 없다.

 

3. string 객체는 변경할 수 없는 타입이다.

자 무슨 말이냐 하면 우리가 평소에 사용하던 연산자를 이용한 문자열 처리가 쓸데없는 가비지를 만든다는 것이다.

예시 소스로 보여주겠다.

 

 

int value = 3;
string msg = "Hello";
msg += "My";
msg += value.toString();

 

 

위와 같은 방법은 아래 방법으로 작업이 이루어진다.

 

 

int value = 3;
string msg = "Hello";
string temp1 = new string(msg + "Hallo");
msg = temp1; //Hello 는 가비지가 된다.
string temp2 = new string(msg + value.toString());
msg = temp2; //Hello, Hallo 는 가비지가 된다
//문자열 "Hello", "Hallo", temp1, temp2 모두 가비지가 된다.

 

보이는가? 몇 개의 가비지 객체가 생성되었는가? 더 이상 볼 수가 없다. 다음의 두 방법으로 해결하자

 

    1. 문자열 보간법을 활용한 코드로 한 번에 문자열을 생성한다.

 

int value = 3;
string msg = $"Hello Hallo {value.toString()}";

 

    2. StringBuilder 클래스를 사용한다.

 

int value = 3;
StringBuilder msg = new StringBulider("Hello");
msg.Append("Hallo");
msg.Append(value.toString());
string finalmsg = msg.toString();

 

StringBuilder는 실제 수정 가능한 문자열을 나타내기 위한 타입으로, 새로운 문자열 생성, 수정, 변경 등의 작업을 수행할 수 있다.

단계적으로 변환 가능한 문자열을 처리하고 최종 적으로 변경 불가능한(immutable) 문자열을 처리할 때 사용하면 된다.

문자열 보간법과 StringBuilder에 대한 선택은 개발자 나름이지만 필자가 찾아본 결과

간단한 문자열 처리는 문자열 보간법

매우 많은 변화의 문자열 처리(50번 이상의 변화)는 StringBuilder가 더 좋다고 한다.

 

결론

1. 자주 할당하는 지역 객체에 대해서 멤버 변수로 옮길지 고려하자.

2. 사용 직전까지 할당을 미루다가 할당 후에는 한번 할당 한 객체를 계속 사용한다.

3. string을 변환시키기보다는 문자열 보간법, StringBuilder를 활용하자.

'Program > Effective C#' 카테고리의 다른 글

Effective C# ITEM 17  (0) 2020.11.08
Effective C# ITEM16  (0) 2020.11.05
Effective C# ITEM14  (0) 2020.10.28
Effective C# ITEM13  (0) 2020.10.28
Effective C# ITEM 12  (0) 2020.10.26

+ Recent posts