[Unity] 프로세스와 메모리
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
프로세스란 프로그램의 실행 단위이다. OS에서 여러개의 프로세스가 동시에 실행될 수 있으므로 우리는 Unity와 브라우저를 동시에 실행할 수 있다.
운영체제는 각 프로세스에 대해 다음을 할당한다.
이들 프로세스는 서로의 메모리를 직접 참조할 수 없다.
C#에서 변수의 주소를 출력하면 나오는 주소 - 예시로 0x0230 으로 나타난 주소가 실제 물리 메모리의 그곳을 의미하지는 않는다는 것에 주의하자. 운영체제는 프로세스에 가상의 메모리를 할당한다. 그리고 그 가상 메모리를 운영체제가 실제 메모리로 매핑한다.
가상 메모리가 가져오는 이점은 아래와 같다.
프로세스의 가상 주소 공간은 이렇게 나뉜다.
스택과 힙, 값타입과 참조타입에 대해서는 이미 다뤘으므로 그 개념을 자세히 풀지는 않겠으나 몇 가지 부연할 것이 있다.
함수가 실행되면 스택에 스택 프레임으로써 그 함수가 들어간다. 이 안에는 헤더와 지역변수들이 들어 있으며, 헤더에는 반환할 주소도 기록된다(함수 실행 결과 다시 호출부로 점프해야하므로). 함수가 끝나 스코프가 사라지면 해당 스택 프레임과 함께 그 안에 지역변수도 통째로 사라진다. 이는 스택 포인터(SP)의 증감이라는 저비용의 할당/해제 과정이다. 또한 프레임 안의 지역 변수에 대한 참조도 베이스 포인터(BP. 현재 프레임의 기준)에 오프셋을 뺀 값*으로 이루어지므로, 스택이 힙보다 빠르다는 말이 바로 이러한 맥락에서 비롯된다.
C#코드의 변수 메모리 주소를 IDE에서 조회한 값은 가상 메모리 주소값으로, 이는 C++도 동일하다. 그러나 C#의 Managed 힙에서는 GC가 자기 판단에 따라 객체의 주소를 옮기고 참조 변수를 갱신할 수 있다. 이는 메모리 파편화와 같은 문제에 대응하는, 최적화를 위해 필요한 과정이다. 따라서 우리는 직접적인 포인터 연산이나 주솟값 참조를 해선 안 되고, 할 수도 없다. 다만 편의를 위해 변수의 주소라는 표현을 쓰고, 참조라는 표현이 권장되기도 한다.
Enemy e = new Enemy();
// ↑참조(스택) ↑객체(힙)
프로세스는 때때로 커널에 자원을 요청한다. 힙 할당, 시간 조회, 스레드 요청, 네트워크 소켓 생성/연결/송수신 등이 그것이다. 이들은 비용이 다양한데, 여기서는 힙 할당에 대해서만 다룬다.
힙에 객체를 생성할 때 메모리가 부족하다면 CLR은 운영체제(커널)에 메모리를 요청한다(시스템콜). 매번 시스템콜을 하는 것은 비효율적이므로 CLR은 일정 단위(세그먼트, 수MB)의 요청을 하며 여기에 객체를 이어서 추가한다. 이어서 추가한다는 것은, 현재 메모리의 끝 뒤에 새 세그먼트를 이어붙이고 그곳의 시작점에 추가한다는 뜻이다.
여기서 Managed 힙의 메모리 파편화의 원인을 짐작할 수 있다. GC가 연속된 객체 중 참조가 없는 객체를 수거할 것이고 그 객체가 있던 공간 자체는 그대로 힙에 남을 것이다. CLR이 빈 공간에 객체를 넣지 않고 메모리 끝에 이어붙이므로 이 빈공간은 일단 메워지지 않는데, 이렇게 빈 공간이 흩어지는 것을 메모리 파편화라고 부른다. 대신 GC가 메모리를 정리하며 빈 공간을 채우기 위해 객체의 주소를 이동하고 참조를 갱신하며(위에서 언급한, 주소값 연산 등이 안 되는 이유), 이로써 메모리가 압축된다.
다만 LOH(Large Object Heap)에 저장되는 비교적 큰 객체(약 85Kb 이상)는 기본적으로 압축을 하지 않는데, 너무 커서 이동 비용도 크기 때문이다. 따라서 너무 큰 객체나 긴 배열을 생성/파괴 반복하는 것이 파편화에 취약하다.
한편 GC압축 이후, 앞서 요청했던 많은 세그먼트들 중 나중에 추가된 세그먼트에는 필연적으로 많은 빈 공간이 존재할 것이다. 이는 os에게 반환될 수도 있고 아닐 수도 있는데, 이것을 결정하는 것도 런타임 환경이다. 닷넷은 비교적 적극적으로 반환한다고 하며, 그에 비해 Mono와 IL2CPP는 거의 반환하지 않는다. 이는 반환 자체도 시스템콜로써 부하를 일으키기 때문인데, 게임 프로세스의 경우 수많은 객체가 생성 및 파괴를 반복하는 특성상 반환이 불리하다. 어차피 나중에 또 요청할 가능성이 크기 때문이다. Unity가 한번 받은 메모리를 반환하지 않는다는 말은 이것을 의미하며, 따라서 게임의 메모리 사양을 체크할 때 Avg가 아닌 Max를 측정하는 것이다. 우리는 Unity 프로파일러에서 Managed 메모리의 Used와 Reserved를 통해 그 수치를 파악할 수 있다.
객체를 new 키워드로 생성하여 Managed힙에 넣는다. 이 객체는 GC가 관리하는 대상이 되고 우리는 참조 변수로 이 객체에 접근한다. (다소 모호. 이 시리즈 다음 장에서 해소됨)
참조는 객체를 가리키는 핸들(handle)이며, 이 핸들은 GC가 관리한다.
스택은 매우 빠르지만 스코프가 제한적이고, 동적인 생성/파괴가 어렵다. 힙은 그 반대의 특징을 보이므로 구분 사용이 필요하다.
간딴한 UnityEngine.Object 구조 정리

Unity 멀티플레이 네트워크의 두 가지 큰 축, 호스트 방식과 데디케이트 서버 방식을 비교 정리
![[Unity] 멀티플레이(1)](https://data.develog.develrocket.com/upload/develog/user_1777093328305/1783691652702-ozcffs/_____2026-07-10_225118.png)
Unity 멀티플레이
간딴한 UnityEngine.Object 구조 정리

Unity 멀티플레이 네트워크의 두 가지 큰 축, 호스트 방식과 데디케이트 서버 방식을 비교 정리

![[Unity] 멀티플레이(1)](https://data.develog.develrocket.com/upload/develog/user_1777093328305/1783691652702-ozcffs/_____2026-07-10_225118.png)
Unity 멀티플레이


