Dictionary 에서 enum 을 Key 로 사용할 때 성능에 대해서

2023. 6. 30. 21:28유니티/성능 관리

결론 요약

 

* 유니티 버전은 21.3.16f1 입니다.

 

1. enum 을 key로 사용해도 Add, ContainKey, Remove 를 사용할 때 GC가 발생하지 않는다. (string도 마찬가지)

2. (작성자가 사용하는) 실제 오브젝트 풀링 코드에서도 실행했을 때 GC가 발생하지 않는다.

3. 본인은 더 좋은 코드 개량을 할 수 있었다!


제가 보던 일부 강의에서는 오브젝트 풀링을 만들 때,

새 객체를 생성할 때마다 해당 객체를 생성해주는 코드를 작성하는 것을 보았습니다.

물론 기초 강의라서 초보자 입장에서는 이해하기 쉽도록 하기 위함이겠죠.

 

저는 그것을 최대한 단순화 시키기 위해서

enum 타입 값을 만들고 그것을 타입으로 지정해 Dictionary에 자동 분배하는 코드를 자주 사용했습니다.

인스펙터 창에서 타입 값만 바꿔주면 자동으로 Dictionary에 분류해주니

정말로 편하게 사용해왔습니다.

 

그러던 중, 최적화 공부를 하면서 Dictionary 에 enum 타입을 Key로 사용하면

GC가 발생한다라는 내용을 보았습니다.

 

2016년도 글 부터 (아마 그 이전부터도 꾸준히 있었겠죠) 2019년도 글까지

관련된 글을 찾아보면 enum 값을 Key 로 사용하면 GC가 발생한다는 내용이었습니다.

 

하지만 그 중에 한 개의 글이 '이제는 enum 을 Key로 사용해도 된다' 라는 내용이었습니다.

그에 대한 검증 사진 자료도 함께 써져 있었지요.

 

해당 글의 내용을 요약하자면 .Net 3.X 이전 버전까지는 키를 찾을 때 ObjectComparer 메서드를 사용했지만

.Net 4.0 이후부터는 EnumComparer 이라는 새로운 방식을 사용해 GC가 발생하지 않는다는 내용이었습니다.

 

이 글을 보고 저도 궁금하지기도 하고, 정말로 문제가 없는지 확인해보고 싶어져서

간단한 테스트를 해보기 위해 직접 코드를 짜보았습니다.

 


유니티 버전은 21.3.16f1 입니다.

아래 실험은 FrameWork 가 아닌 Standard 로 실행해 보았습니다.

(Standard 가 FrameWork의 축소판 정도라고 하며, 유니티 기본 세팅으로 되어있습니다.)

 

 

우선 간단한 코드입니다.

enum 을 만들고, 그것을 Key로 사용하는 Dictionary를 만들어 주었습니다.

 

Update 에서는 매 프레임마다 다음을 실행합니다.

1. 타입 별 데이터 추가

2. 타입 검사

3. 타입 삭제

 

string 은 GC가 발생하는지 비교를 위해 넣었습니다.

우선은 위 코드대로 실행을 하고 프로파일러를 확인해보았습니다.

 

로그를 찍기 위해 사용한 코드에서 GC가 발생합니다.

이번에는 코드를 다음 사진과 같이 수정하고 실행해 보겠습니다.

 

이번에는 말끔히 사라졌네요.

 

 

추가로 테스트를 위해 String 을 키 값으로 사용하는 Dictionary 도 만들어보았습니다.

string 타입 key 도 발생시키지 않는 것 같네요.


그러면 제가 자주 쓰는 오브젝트 풀링 방식으로도 사용해 보겠습니다.

개인적인 방식이므로 더 좋은 방향이 있다면 지적 감사하겠습니다.

 

간단한 Enemy 클래스도 만들어 주겠습니다.

이 Enemy 클래스는 테스트용 프리팹이 맛있게 먹었습니다.

 

프리팹들이 담기는 리스트와

각각 프리팹이 담길 사전, 풀링을 위한 사전을 만듭니다.

 

초기화는 다음과 같이 합니다.

리스트는 인스펙터 창에서 채우므로 따로 new 선언할 필요가 없고

사전을 먼저 선언한 뒤에 리스트의 값에 따라 프리팹을 정렬합니다.

 

제가 주로 사용하는 코드는 이런 느낌 입니다.

Enemy 클래스에 포함된 TestType 타입을 사용하는 enmyType 변수 값을 기반으로

사전에서 필요한 적을 찾아 복제하거나 큐에서 꺼내 사용합니다.

 

 

이것을 Update 에서 사용해보겠습니다.

액티브를 할 때를 제외하고는 GC가 발생하지 않네요.


이번 실험은 어디까지나 GC 의 발생 여부에 대해서 알아보는 것이고

메모리와 같은 다른 성능 비교는 해보지는 않았습니다.

 

하지만 전체적으로 GC가 크게 발생하지 않는 결과를 얻을 수 있었습니다.

개인적으로는 추가 결과로, Enqueue 의 GC 호출이 일어나지 않는 다는 점을 알았기에

중간에 프리팹을 분류하는 Dictionary를 줄이고, 프래팹의 복사본을 바로 풀에 넣은 뒤

큐가 모자랄 때에는 꺼낸 복사본을 다시 하나 복사해 큐에 넣는 방식도 사용해 볼 수 있었습니다.

 

이 방법을 써보니 초기화에서 이미 모든 타입의 키값이 생성되므로

마지막에 있었던 CreateKey 메서드도 2번이나 호출되던 것을 실행하지 않아도 되어서

좀 더 효율이 오른 것 같습니다.

 

이래저래 유익한 실험이었던 것 같습니다.

'유니티 > 성능 관리' 카테고리의 다른 글

리소스에 대해  (0) 2023.05.14
게임 오브젝트 찾기(GameObject.Find())에 대해  (0) 2023.05.14