[CSAPP&Unity #3] 흔한 걸 좀 덜 쓰는 방법

혼자 리서치하다가 재밌는 두 가지가 있어 쓴다. 우선 두 가지 질의 응답을 한번 고민해보자.


  • 모든 경우의 GetComponent의 횟수는 가능한 적게 쓰는 것이 좋다고 알려져있다. 그 이유 중에서 GC를 고려한 것이 있을까?
  • 마이크로소프트 이야기로는 구조체의 크기는 16 바이트 이하인것이 오버헤드를 덜 발생시킨다고 했다. 왜 16바이트인지에 대해 생각해보자.

GetComponent<>에 대한 답

GetComponent 계열의 함수가 각각 발생시킬 수 있는 오버헤드는 많이 다르다. GC와 연관이 있을수도 없을 수도 있지만, 적게 쓰는 큰 의의는 검색 비용때문이다. GC.Alloc을 유발하는 경우를 소개한다.

GetComponents<T>() 는 복수형이다. 가장 명확한 예제로, 이 메서드는 컴포넌트들을 담을 새로운 배열을 매번 생성해서 반환한다. 이 배열은 힙에 할당되며, 사용 후 GC의 수거 대상이 된다.

  • GetComponentsInChildren<T>(List<T> results) 처럼 리스트를 미리 만들어놓고 재사용하는 형태를 고려 할 수 있다. 이를 통해 불필요한 GC 할당을 피할 수 있다.

GetComponent(string)은 문자열 이름을 통해 컴포넌트를 찾는 방식이다. 예로부터 문자열 기반 검색은 내부적으로 문자열 비교 및 처리, 리플렉션을 수행하는데 이 과정에서 GC.Alloc을 생성 할 수 있다. 또한, Generic과 비교할 때 훨씬 느리다.

GetComponent(Type), 이렇게 써본적은 없지만 GetComponent(typeof(Rigidbody)) 같이 쓴다고 한다. 이 역시 Generic과 비교하면 오버헤드가 있으며, 오래된 유니티에선 가비지를 생성 할 수 있다.

(논외로 6000.2 이하 버전은 보안 이슈때문에 사용해선 안된단다. 그걸 감안하면 지금 오래된 버전을 논한건 의미가 없을 수도 있다)

 

GC를 유발하지 않는 경우를 소개한다.

제일 흔한 GetComponent<T>()는 Generic에 단수형이다. 이 메서드는 내부적으로 매우 최적화되어 관리되는 힙에 가비지를 생성하지 않는다. 하 지 만 !! GC랑 상관이 없더라도 CPU 연산 상 매우 비싼 편이다.

GetComponent가 비싼 진짜 이유는 CPU 비용 때문인데, 이건 예전 글 #1에서 설명한것과 완전히 같은 내용이다.

https://hyeonistic.tistory.com/247

 

[CSAPP&Unity #1] 캐시 지역성을 고려한 실전 로직 만들기

CSAPP을 공부했지만 구체적으로 활용할 기술스택에 입혀야 완전체가 된다고 생각한다. CSAPP을 안다고 끝일리가 없는데, 다소 늦게 시작한 만큼 가치 있는 글을 만들고자 한다.GetComponent와 시간적

hyeonistic.tistory.com

 

간단하게 요약하면, 매번 검색하기 때문이다.

이로써 GC랑 연관이 있는 내용을 보았다. GC가 없는 이상적인 게임을 만들고 싶은만큼 이에 대한 대체재를 항상 고려하는 것은 좋은 습관이 될 것이다.

 

구조체 16바이트

마이크로소프트 입장에서 클래스를 써야하나요 구조체를 써야하나요? 답이 있다.

  • 논리적으로 단일 값을 표현하는 경우
  • 16바이트 이하의 크기를 가진 경우
  • 불변 타입으로 대우하려는 경우
  • 박싱 연산이 자주 발생하지 않는 경우

네 개를 만족하는게 아니면 클래스를 쓰세요. 라고 한다. 근데 진짜 16바이트라는 구체적 수치에 너무 태클을 걸고 싶었는데, 여기서 어른들의 사정 내지 게임사가 어찌 할 수 없는 구간에 드디어 도달하게 된다.

문제는 CPU의 명령어 셋의 데이터 이동 단위가 32바이트냐는 것이다. 솔직히 우리는 2의 n승의 수치에 익숙하기 때문에 16바이트 말고 17바이트는 안돼? 라는 말은 좀 부자연스럽다. 그럼 16바이트의 다음으로 고려 할 수 있는 것이 32바이트인 것인데..

컴퓨터의 CPU 스펙을 보면 다음과 같은 내용을 꼭 확인 할 수 있다 : 해당 사진에서 지원되는 확장 란을 확인해보자. 이 내용은 Ryzen 7 7800X3D의 실제 상품 화면이다.

우리가 봐야 할 것은 SSE와 AVX이다. SSE 레지스터를 사용하면 128비트를 레지스터 하나로 처리 할 수 있고, 128비트는 16바이트에 대응하기 때문에 이것이 구조체의 권장 크기가 된 것이다. 쉽게 이야기하면, 한번에 옮기는 단위가 16바이트까지 였기 때문이다. 그래서 16바이트의 코딱지만큼만 넘어가도 이야기가 달라지는 것. 두 번을 옮겨야하는 것이다.

하지만 AVX 명령어도 지원이 시작된지 오래된 명령어 셋 중 하나로, 이는 32바이트 단위도 가능한 편이다. 근래의 컴퓨터들은 256비트 크기를 한번에 옮길 수 있는 레지스터들이 있어서 이에 대한 대응이 충분히 가능한 편이다.

이에 대한 제한이 보다 풀려있다.. 라고 말하기엔 확답하기가 좀 그런것이, 모바일 대응 문제가 있다.

현재의 게임 시장에서 유니티는 모바일 특화로 개발되는 것이 주류이다 : 즉, 이것은 PC의 SSE고 AVX를 논할게 아니고 ARM 진영에 있는 명령어를 봐야한다.

근데 ARM에는 256비트 단위의 데이터를 옮길 수 있는 레지스터가 아직 없다. 2025년 기준이고, 솔직히 말하면 지금도 모바일에서 기깔난 게임이 나오는 마당에 256비트 레지스터에 대한 수요는 많이 오래 걸릴 것이다.

모바일 대응은 그냥 빌드를 딸깍해서 완료했다면 사람들 휴대폰에서 작동은 할 것이다. 다만 PC 수준의 널널한 프레임을 목표로 한다면 구조체를 볼게 아니라 드로우콜 감소와 GC 감소 시키는게 좀 더 의의가 있을 것이라고 생각한다.

'컴퓨터 이론 > Unity' 카테고리의 다른 글

유니티 기본 개꼬여서 정리  (0) 2025.10.24
Deepdiving Garbage Collection : GC #2  (0) 2025.10.22
Deepdiving Garbage Collection : GC #1  (0) 2025.10.16