본문 바로가기

삽질의 기록

[Unity] UI Text 가비지 제거, 삽질의 기록

※ 해당 글은 ToString()으로 인해 발생한 가비지를 제거하려는 시도로, 삽질이 많습니다. 결론은 3. 결과부터 참고하시면 됩니다.

 

 

 

테스트 시 자주 프레임 드랍이 되는 것을 목격하고, 유니티 프로파일러로 분석하고 있었다. 원인은 자주 일어나는 가비지 콜렉터 실행. 프로파일러로 가비지가 쌓이는 부분을 추적했다.

 

UIManager.UpdateScorePanel() 함수에서 24B의 가비지가 쌓이고 있다.

 

void UpdateScorePanel()
{
	scoreText.text = GameManager.Instance.score.ToString();
}

 

해당 함수의 내용은 위와 같았다. int값인 score를 ToSting()으로 변환시킬 때마다, 가비지가 생성된 것.

이 함수는 플레이어가 점수를 얻을 때마다 실행되므로, 계속된 플레이 시 부담이 클 것 같았다.

 

그래서 이 부분을 없애보기로 하였다.

 

 

 

 

 

 

 

 

 

 

0. 가비지가 생성되는 이유?

C#의 string은 불변 객체다. 즉, 한 번 생성되면 다시는 수정할 수 없다. 그럼에도 +나 Concat()으로 수정이 가능했던 건, 사실 새로운 string을 만드는 것이었다. 기존의 string은 그대로 GC의 먹이가 된다.

 

이 가비지를 없애기 위한 대표적인 방법은 StringBuilder다.

 

 

 

 

 

1. StringBuilder 사용하기

StringBuilder는 C#에서 제공하는 클래스이다. 위치는 System.Text.

 

기존 string은 상술했듯 변경 시마다 새로운 문자열이 생성되는데, StringBuilder는 별개의 클래스이기에 새로운 string이 생성되지 않는다.

 

수정한 코드는 다음과 같다.

 

StringBuilder scoreSB = new StringBuilder();

void UpdateScorePanel()
{
	scoreText.text = UseStringBuilder(GameManager.Instance.score);
}

string UseStringBuilder(int i)
{
	scoreSB.Clear();
    	scoreSB.Append(i);

	return scoreSB.ToString();
}

 

scoreSB를 미리 생성해두고, UpdateScorePanel()에서 호출할 땐 Clear로 내용만 지우고 Append로 새로 작성했다.

 

희망을 품고 돌린 결과는 아래와 같았다.

 

그렇게 쉽게 해결됐으면 프로그래밍이 아니다.

 

가비지는 48B로 두 배로 늘어났다. 성능은 두 배로 떨어졌다. 그래도 괜찮다. 한 번에 해결될 거란 기대는 안 했다.

 

원인을 알아내기 위해 상세하게 파고 들어가봤다.

 

Append에서도 가비지를 생성해냈다.

 

StringBuilder를 string으로 변환 후 리턴하는 과정에서 ToString()이 쓰였다. 결과적으로 좀 더 복잡하게 ToString을 쓴 것과 다름없었다. 게다가 Append에서도 가비지가 생성되었다.

 

그래서 우선, ToString을 없애보기로 하였다.

 

 

 

 

 

2. TextMeshProUGUI.SetText()

영어로 구글링을 하며, 나는 SetText()라는 함수를 찾아냈다. 해당 함수의 위치는 다음과 같다.

 

TMPro.TMP_Text.SetText(StringBuilder sourceText)

using TMPro로 사용할 수 있다.

 

인자를 보면 알겠지만, 이 함수는 StringBuilder를 그대로 받는다. 따라서 StringBuilder - string 변환 가비지가 생성되지 않는다.

이번엔 진짜 희망을 품고 프로파일러를 돌려보았다.

 

누군가 말했다. 기대를 하니까 배신당하는 거라고.

 

가비지는 무려 6.4KB! 바이트로 환산하면 6400B가 되었다. 처음에 비해 약 267배 커졌다.

 

그러나 유니티 포럼의 영문 글들에 따르면, SetText는 가비지를 생성하지 않는다고 나와 있다.

여기서 나는, 한 가지 오류를 발견했다.

 

지금껏 내가 측정했던 프로파일링은 게임이 시작되는 순간이었다. 즉, 실제 플레이가 진행되면서 쌓이는 가비지를 측정하지 못했던 것이다.

 

 

 

그래서 나는 다시 처음부터, 점수 오브젝트를 먹었을 시점을 기준으로 프로파일링을 새로 했다.

 

 

 

 

 

 

 

 

 

 

3. 결과

 

좌 : ToString으로 직접 텍스트에 할당, 30B 가비지 생성, 우 : SetText로 할당, 100B 가비지 생성

결과는 참담했다. 그나마 다행인 점은, TMP_Text.InternalTextBakingArrayToString() 함수는 유니티 에디터에서만 실행된다는 점이다. 즉, 실제 구동 시엔 저 70B의 가비지는 생성되지 않을 것이다.

(함수에 커서를 두고 F12를 누르면 원본으로 이동할 수 있다. 그곳에서 #if UNITY_EDITOR임을 발견했다.)

 

문제는 StringBuilder.Append(Int32) 함수다. 마이크로소프트 공식 문서에 따르면, 해당 함수 호출 시 내부적으로 Int32.ToString()이 실행된다.

즉, 처음 작성한 코드와 수정한 코드엔 아무런 성능 차이도 없다.

 

 

 

 

마음을 가라앉히고, 차분히 생각해보았다. 가비지는 ToString에서 발생하고 있다. 이는 정수를 문자열로 변환시키는 함수며, 당연하게도 새로운 string이 반환된다.

 

따라서, ToString()의 가비지를 없앨 방법은 현재로써는 없다고 보면 되겠다.

 

 

 

해당 이슈는 문자열 결합으로 발생한 것이 아니기에, StringBuilder를 쓴다고 더 좋아질 수 없었다. 나는 그걸 이제서야 깨달았다.

그래서 나는, 그냥 이대로 쓰기로 했다. 지금은 이보다는 다른 부분의 가비지를 줄이는 게 성능 향상이 될 것이므로.