Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019

  • Login to see the comments

이무림, Enum의 Boxing을 어찌할꼬? 편리하고 성능좋게 Enum 사용하기, NDC2019

  1. 1. Nexon Korea 이무림 편리하고 성능좋게 Enum 사용하기 Enum의 Boxing을 어찌할꼬?
  2. 2. 발표자소개 ⚫ 넥슨 데브캣 스튜디오 MT팀 ⚫ <런웨이 스토리> ⚫ <로드러너 1> ⚫ <링토스 세계여행> ⚫ <마비노기 2> ⚫ <마비노기 360> ⚫ <루니아 전기> 이무림
  3. 3. 목차 ⚫ Enum 과 Boxing ⚫ 정석적 해법 ⚫ Generic Enum 과 Int 사이의 변환 ▪ 동적 코드 생성 ▪ C++ Union 트릭 ▪ Unsafe Pointer 트릭 ⚫ EnumDictionary
  4. 4. Enum 과 Boxing
  5. 5. Boxing 이란? ⚫ 값 타입을 참조 타입으로 변환 ▪ ex) int -> object ⚫ 값 타입은 Stack 메모리에 위치 ⚫ 참조 타입은 Heap 메모리에 위치 ⚫ Heap 에 공간을 할당해서(Box) Stack 에 있는 값을 복사해 넣음
  6. 6. Boxing 예제
  7. 7. Boxing의 뒷처리 ⚫ 1회성으로 생성된 수많은 Box는 Gabage로 변한다. ⚫ Gabage Collection 이 자주 필요하게 됨 ⚫ GC가 발생하면 게임이 멈칫.
  8. 8. Enum과 Generic의 만남 ⚫ Enum 타입을 Generic으로 받으면 비교할 때 Boxing을 피할 수가 없다.
  9. 9. Enum과 Generic의 만남 ⚫ System.Object.Equals() 메소드가 사용되기 때문 ▪ object로 인자를 전달받기 위해 오른쪽 객체가 Boxing된다. ▪ virtual 메소드이므로 왼쪽 인자가 참조객체로 Boxing된다.
  10. 10. Enum과 Generic의 만남 ⚫그런 코드 사용 안하면 되지! ⚫그런데 정말 사용하지 않을 수 있을까?
  11. 11. Enum과 Generic의 만남 ⚫Dictionary<K, V> 에 Enum Key가 들어간다면? ▪ 설마… MS가 이런 것 하나 알아서 안 해주겠어? ⚫설마설마 하던 그 설마가…… Boxing 발생.
  12. 12. List<T> 너마저도? ⚫ 기본중에서도 기본 컨테이너 List<T>는 Enum 을 잘 다룰 수 있을까? ⚫ Contains(), Remove(), IndexOf()처럼 내부적으로 Equals()를 호출하는 경우 동일하게 Boxing 발생
  13. 13. Enum.HasFlag() ⚫ HasFlag() 메소드는 편리한 Bitmasking 처리를 해준다. ▪ .Net 4.5 에서 사용가능
  14. 14. Enum.HasFlag() ⚫ HasFlag() 함수의 내부를 살펴보자. ⚫ Boxing 이 발생한다.
  15. 15. Box의 크기 ⚫ Boxing이 발생하면 메모리를 얼마나 차지할까? ⚫ 값 타입의 최소 크기는 4 byte ⚫ 참조 타입의 최소 크기 12 byte ▪ 32bit System 기준 ⚫ Equals() 호출시 Boxing 2번 발생 ⚫ 총 24 Byte 사용 ⚫ 한 프레임에 40번 비교하면 24 x 40 ~= 1 Kbyte Gabage 발생
  16. 16. 정리: Enum과 Boxing ⚫Boxing은 성능에 악영향을 미친다. 최대한 줄여야 한다. ⚫Enum과 Generic이 만나면, 비교과정에서 Boxing이 발생한다.
  17. 17. 정석적 해법
  18. 18. 정석적 해법 ⚫ 표준 컨테이너는 생성자에서 Comparer 를 전달받을 수 있다.
  19. 19. 정석적 해법 ⚫ Enum 타입 하나당 Comparer 타입 하나를 구현해야 한다. ⚫ 컨테이너를 생성할 때마다 Comparer 를 전달해야 한다. ⚫극한직업: 프로그래머 편 ⚫ 이것은 해법인가? 노가다인가?
  20. 20. C# 최신 버전이라면? ⚫ C# 7.3 부터 Generic Contstraint 로 Enum 을 지정가능 ⚫ T 가 Enum 인 것을 알고 있으니 Equals()를 최적화 해주겠지?
  21. 21. C# 최신 버전이라면? ⚫ IL 을 확인해보자. (Release 빌드) ▪ box 명령어에서 한번 ▪ constrained.callvirt 내부에서 box 발생
  22. 22. 정리: 정석적 해법 ⚫정석적인 방법은 너무 번거롭고 손이 많이 간다. ⚫최신 스펙의 Enum Generic Constraint 를 이용해도 Boxing이 발생한다. ⚫다른 방법이 없을까?
  23. 23. Generic Enum 과 Int 사이의 변환
  24. 24. Generic Enum 과 Int 사이의 변환 ⚫ Enum은 그저 숫자에 불과하다. ▪ BackField 타입을 지정할 수 있다. ▪ 아무것도 없으면 int ⚫ Int 로 변환할 수 있다면 Boxing 없이 비교할 수 있다. ▪ 일반화된 Bitmasking처리도 가능하다.
  25. 25. Generic Enum 과 Int 사이의 변환 ⚫ 개별적인 Enum타입은 쉽게 int 로 변환할 수 있다. ⚫ Generic 으로 받은 Enum은 변환이 어렵다. ⚫ 방법을 찾아보자.
  26. 26. 동적 코드생성
  27. 27. 동적 코드생성 ⚫ 개별적인 Enum타입은 쉽게 int 로 변환할 수 있다. ⚫ Generic 으로 받은 Enum은 변환이 어렵다. ⚫ Generic 타입의 구체타입이 무엇이 될것인지 컴파일 시점에서는 확정할 수 없기 때문. ⚫ 프로그램이 실행되는 중에는 구체적인 타입이 무엇인지 알고 있다. ⚫ 즉시 코드를 생성해서 변환한다.
  28. 28. 동적 코드생성 ⚫ 런타임에 개별 enum 타입을 캐스팅하는 코드를 생성한다. * ValueCastTo<TTo> 클래스로 분리하지 않으면 유니티 구버전에서는 컴파일 에러가 발생한다.
  29. 29. 동적 코드생성 ⚫ 사용법 ⚫ 동적 코드 생성이라니, 기술 그 자체로 멋있다! ⚫ 문제 해결! ⚫ 하지만 AOT(Ahead Of Time) 플랫폼에서 동작하지 않는다. ▪ 망해라 애플
  30. 30. C++ Union 트릭
  31. 31. C++ Union 트릭 ⚫ C#의 조상언어인 C++에는 Union 타입이 존재한다. ⚫ Union에 선언된 필드는 같은 메모리를 액세스한다. ⚫ C#은 Union 타입을 지원하지 않지만 [StructLayout], [FieldOffset] 특성을 사용하여 흉내낼 수 있다.
  32. 32. C++ Union 트릭 ⚫ C++ Union 스타일로 필드를 겹치게 배치해서 읽고 쓴다.
  33. 33. C++ Union 트릭 ⚫ 잘 동작한다! ⚫ 성능도 너무 좋다! ⚫ 그런데 강타입 언어가 이렇게 쉽게 타입체크를 우회해도 되나?
  34. 34. C++ Union 트릭 ⚫ 런타임 구현에 따라 다르다. ⚫ .Net 3.5 Equivalent 에서는 잘 동작 ▪ 내부적으로 Mono 2.6.5 런타임 사용 ⚫ .Net 4.x Equivalent에서는 부작용 존재 ▪ 내부적으로 Mono 4.2.2 런타임 사용 ⚫ 이 기법을 적용한 Assembly에 Reflection을 시도하면 예외가 발생한다. ▪ System.TypeLoadException: Generic class cannot have explicit layout.
  35. 35. Unsafe Pointer 트릭
  36. 36. Unsafe Pointer 트릭 ⚫ C#에서도 C++처럼 포인터를 사용할 수 있다. ⚫ 단, unsafe 블록 안에서만 가능. ⚫ int 형 포인터를 이용해서 Enum 위치를 읽어들이자.
  37. 37. Unsafe Pointer 트릭 ⚫ 포인터를 이용해서 Enum 필드에 접근한다.
  38. 38. Unsafe Pointer 트릭 ⚫ 잘 동작한다. ⚫ 컴파일시 -unsafe 스위치가 있어야 컴파일이 가능하다. ⚫ Unity2017.x 에서는 unsafe 가 디폴트 ⚫ Unity2018.x 에서는 설정에서 선택가능 * 유니티 구버전에서 unsafe 플래그를 지정하려면 Assets 폴더에 mcs.rsp, csc.rsp 파일을 생성하고 내용에 -unsafe 를 추가한다.
  39. 39. EnumDictionary
  40. 40. EnumDictionary ⚫ Enum을 Key로 사용해도 Boxing이 발생하지 않는 Dictionary ⚫ Enum을 내부에서 int 로 변환해서 저장하고 내보낼 때 다시 Enum타입으로 변환한다.
  41. 41. EnumDictionary ⚫ 구현
  42. 42. EnumDictionary ⚫ #if 를 사용해서 플랫폼에 따라 구현을 선택한다. ⚫ 기본적으로 동적 코드생성을 사용 ⚫ Unity 환경에서는 ⚫ .Net 3.5 에서는 C++ Union 트릭사용 ⚫ .Net 4.x 에서는 Unsafe Pointer 트릭사용 ▪ 2018.x 버전에서는 allow unsafe code 옵션을 켜고 쓴다.
  43. 43. EnumDictionary ⚫ 사용법 ⚫ 너무너무 편리하다.
  44. 44. EnumDictionary ⚫ 전체 코드 ▪ https://github.com/netics01/EnumDictionary ⚫ BitConvert ▪ 모든 Enum 과 Int 변환방식 구현 ⚫ EnumDictionary ⚫ EnumHashSet ⚫ 데브캣에서 사용하는 SUF의 일부 ▪ Silvervine Unity Framework * 64bit BackField 를 사용하는 Enum 에는 사용할 수 없다.
  45. 45. 감사합니다.

×