[Unity] 우리가 만지는 대부분(?)의 메모리 (Stack/Heap/Native)
![[Unity] 우리가 만지는 대부분(?)의 메모리 (Stack/Heap/Native)](/_next/image?url=https%3A%2F%2Fdata.develog.develrocket.com%2Fupload%2Fdevelog%2Fuser_1776858831954%2F1776865277611-wfmf1n%2F2026__4__22_____10_40_19.png&w=3840&q=75)
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요!
Managed Heap, Native Memory, 그리고 하나 뭐였는지 이름이 기억 안 나는데 일반적으로 개발할 때 건드릴 일은 없다. Unity에 태초부터 존재하는(...) 것들을 저장하는 것으로 기억한다. 아직은 관심없다(ㅎㅎ;;)
아무튼 일케 크게 3종류 있다. 수업에서는 각각의 개념 정도만 간단히 배웠다.
수업에서 배운건 Managed Heap/Native/(기억 안나는 무언가) 였는데 제목은 Stack/Heap/Native다.
Heap과 GC 얘기를 하는데 빠트리기 미안해서 기억 안 나는 것 대신 Stack을 넣었다(...)
GPT가 기억 안 나는 나머지 하나가 기타: Graphics Memory, Job/Temp 등 세부 영역이라고 한다. 메모리를 3등분 할 때 Stack이 끼어있는 게 마음에 안 드신다고 하신다.
스택이다.
젠가는 중간에서 뺄 수 있지만, 이건 박스 안에 가지런히 정리한 젠가라서 위에서밖에 못 뺀다.
고급지게 후입선출 또는 LIFO 구조를 갖는다고 말한다. 마지막에 넣은 걸 가장 먼저 빼게 되어있다는 뜻이다.
CPU는 이 메모리 공간 안에 있는 녀석들을 참조할때, 어차피 위에서밖에 못빼니까, 가장 위에있는 것을 화살표로 가리키고 있다. 그리고 사용자가 그걸 빼면, 화살표를 한 칸 내린다. 사용자가 뭔가 넣으면, 화살표를 한 칸 올린다.
이게 CPU가 각각의 stack 메모리에 접근하기 위한 전부이다(진짜임). 고급지게, 캐싱 효율이 좋다고 말하고, 넣고 빼고 둘 다 O(1)만큼 걸린다.
오케이 스택 끝 이제 언급 안 함.
우리가 아는 그 힙인데, 닷넷 기반이라 managed고 GC를 쓴다.
C#은 마소의 .Net 프레임워크 위에서 돌아가는 언어인데, Heap 메모리를 관리하기 위해 C계열 언어의 delete 대신 GC를 도입했다. GC도 종류는 많지만 얼추 비슷비슷하다. 최근에 Unity버전업하면서 GC도 바꾼다고는 했던거같은데 그건 나중에 더 자세히 생각하자. 아무튼 GC에 의해 관리되기에 Managed Heap이라고 부른다.
Job시스템 쓰면 보이는 NativeArray 가 딱 이거다.
이 메모리는 직접 해제해줘야하는 메모리이다. GC가 관리 안 한다.
NativeArray 구조체의 .Dispose() 메소드가 그것이다.
Heap할당시 Unity는 메모리에 세그먼트를 잡고 오브젝트를 "적당히" 집어넣는다. 세그먼트 내에 빈 공간(페이지)이 있거나 앞서 GC.Collect된 공간이 있으면 그 자리에 넣는 개념이다. 그리고 때때로 메모리를 순회하며 닿지 않는 것들에 대해 "이 오브젝트 죽음!" 처리를 한다. 당연히 순회하는 동안 시간이 오래 걸린다. 그리고 이 시간 동안 모든 스레드가 정지한다(Stop-the-world. 초기 Boehm 방식. 사실 GC마다 다름) 아무튼, "이 오브젝트 죽음!" 처리는 메모리를 해제하는 것과는 조금 다르다. 자세히 다루기에는 너무 길지만, 대충 이런거다.
"이거 죽었는데 다음 프레임에서 또 쓸 수도 있으니까 OS님 잠깐만 내가 최대 6번의 GC.Collect()를 할 때까지 기다려요! 나 진짜 잠깐 비우는거야 이따 언페이징 할게!!"
더 자세히 알고 싶다면 이 영상 Unity Korea을 참고하자. 최근에 올라온 아주 따끈따끈한 영상이다. 근데 좀 어렵다.
Native는 할당시 메모리에 적당히 집어넣지는 않는다. 애초에 넣을 오브젝트를 변환된 컴포넌트 단위로 쿼리해서 일괄 처리(IJobForeach 같은 느낌) 하기때문에 메모리에 이쁘게 집어넣는다 - DOD방식. 이는 CPU의 캐싱 효율을 증가시키므로 기존 OOP방식의 힙 할당보다 빠르다. 대신 이쪽은 GC가 관리하는 영역을 벗어난 곳이기 때문에, 직접 해제가 필요한 것으로 알고 있다. OS공부를 딥하게 하지 않아서 캐싱 효율 증가가 실제로 얼마나 대단한 것인지 몰랐는데, 써보니까 대단하긴 했다. 몇 천 개의 움직이는 오브젝트를 곧잘 렌더링한다.
아무튼 구체적으로 어떻게 쓰는지에 대해서는 대충 이렇다.
System: [쿼리] "transform이랑 rotation이랑 Projectile 컴포넌트 갖고 있는 엔티티?"
Entity들: [쿼리 반환] "저요!"
System: [할당] "자 줄서세요. 너희들을 NativeArray 에 할당할게요"
System: [JobForeach] "NativeArray 은 MyGenerateJob.Execute()에 갔다오시고"
System: [JobForeach] "NativeArray 는 MyMoveJob.Execute()에 갔다오시고"
System: [JobForeach] "NativeArray 은 RotateJob.Execute()에 갔다오시고..."
System: [해제] "모든 Job이 끝났네요~ Projectile은 필요 없으니까 Dispose할게요"
System: "나머지 transform, rotation은 Job.OnUpdate()에서 계속 쓸거에요."
자세한 건 이 영상 NDC(넥슨 디벨로퍼 컨퍼런스)이나 이 영상 Unity Korea을 참고하자. 참고로 최신 Unity 6.4버전의 ECS 1.0은 다른 영상을 보는 게 낫다. 다들 옛날 영상들이라...
끗.