본문 바로가기

유니티 개발 정보/개념

유니티 메모리 관리 - 1

원문이 약간 길어 3~4개로 나눠어서 올릴 예정입니다.
해당 글 원문 http://unitygems.com/memorymanagement/에서 보실수 있습니다.
번역중에 이상한 부분은 댓글 남겨주시면 확인 후에 수정하겠습니다.



만약 당신의 게임이 실행되고 있는 컴퓨터나 장치의 

메모리에서 떤 일이 일어나는지 궁금했던 적이 있다면 

이 글은 당신에게  좀 더 효율적인 게임을 만들고 클래스와

함수의 메모리사용을 최적화하는데 도움을 줄 것이다.

이번 글에서는
  • 메모리 구역
  • 값타입과 스택
  • 참조타입과 힙
  • 구조체와 클래스
  • 참조타입을 만드는 것
  • 정적 클래스
  • 정적 변수
  • 정적 함수
  • 힙 파편화, 오브젝트 풀링, 가비지 컬렉션
이 튜토리얼은 유니티 개발을 위해 C#을 사용하게된 C/C++ 프로그래머에게 특별히 유용하다. C#은 처음에는 혼란과 마법같은 방식으로 메모리를 다루고, 그것은 그것 자체의 위험과 특이한 방식을 가지고 있다.
유니티 프로그래밍을 시작할 때 그리고 일반적인 프로그래밍을 시작할 때 변수 타입의 용도에 많이들  골머리를 앓게 될 것이다. 예를 들어, 왜 어떤때는 변수들이 업데이트되고 다른 변수들은 똑같은 값으로 유지되며, 왜 내가 여전히 필요로 하는 변수는 사라지는지 말이다.

또 다른 초심자의 공통적인 문제는 정적 변수의 사용이다. 인터넷의 다양한 튜토리얼에서 정적 변수를 종종 만나지만, 강사는 시간을 들여 명확하게 설명을 해주지 않으며, 오직 "변수에 대한 쉬운 접근"이라고만 말한다.

문제는 정적 변수(다르게 클래스 변수라고도 불리는)는 쉬운 주제가 아니라는 것이다.

몇몇은 심지어 정적 변수로 작성된 것은 다른 타입으로 처리가 될 수 있다며, 그것을 사용하는 것을 피하라고 한다. 우리는 그것이 진실인지, 그것이 어쩔수 없는 최고의 해법이 아닌지에 대해 알아 볼 것이다.


시작하기에 앞서, 당신이 C 프로그래밍에 대한 지식을 가지고 있거나 객체지향 프로그래밍에 대해 잘 알지 못한다면, 당신이 메모리 관리와 저장 키워드에 대해 알고 있는 것은 OOP(객체지향 프로그래밍)에서는 전체적으로 유사하지 않다는 것을 명심해라.
C#의 C를 연관시키지 않으려고 노력해라.
이 튜토리얼에서, 우리는 각각의 메모리 지역을 다룰것이며, 값형식, 참조형식, 동적메모리 할당 그리고 정적 변수도 다룰것이다.


메모리 지역

프로그램이 시작할 때, 운영체제는 메모리의 일정부분을 프로그램에 할당한다.  할당된 메모리에는 4개의 메모리 지역이 존재하는데, 콜 스택, 힙, 레지스터 그리고 정적 메모리 지역이다.

C#언어 개발자는 친절하게도 4개의 메모리 지역을 2개로 좁혔는데, 2개의 지역이 바로 
스택과 힙 영역이다. 스택은 순서가 있고 빠르지만 제한적이다. 힙은 무작위이고, 크지만 느리다.

프로그래머는 각각의 변수에 어느 메모리 영역을 사용할 것인지를 변수를 사용하는 목적과 변수의 생명주기를 고려하여 결정할 수 있다.

메모리 할당을 위한 3개의 키워드가 있다. 바로 auto, static(우리가 이제 막 다룰려고 하는 것), extern이다.
(C언어에서는 여기에 register라는 키워드가 추가되어 총 4개의 키워드가 존재한다.)

extern 키워드는 아마 본적이 있을 것이다. 그것은 당신의 프로젝트에 있는 다른 코드에 선언된 변수를 선언한다는 의미이다. 
예를 들면 .NET 언어가 아닌 다른 언어로 쓰여진 DLL파일에 포함된 변수가 있다.


만약 당신이 C 프로그래머라면, extern의 의미는 C#과 미묘하게 다르다는 것을 알아야 한다. C#에서는 managed memory에 할당되지 않는 변수를 참조한다.

*(역주: managed memory와 unmanaged memory에 대해)
이것은 모두 똑같은 물리적 메모리이다. 차이는 단지 누가 그것을 제어하는가이다.
마이크로소프트는 managed memory를 Garbage Collector(GC)에 의해서 정리된다고 정의한다. 예를 들어, 정기적으로 물리적인 메모리의 어떤 부분이 사용되고 사용되지 않는지를 알아내는 프로세스가 여기에 해당된다.

unmanaged memory는 그밖의 다른 것에 의해서 정리된다.
예를 들어 당신의 프로그램자체내에서 또는 운영체제가 여기에 해당된다.
1
extern int number;

컴파일러는 number를 메모리에 할당하지 않는다. 그리고 변수는 다른 어느 곳에서 발견 될 것이라고
예상한다. 우리는 더이상 이 주제에 대해서 연연에 할 필요가 없을 것이다.
이 링크에서 더 많은 정보를 찾을 수 있을 것이다.


값형식과 스택

auto는 automatic 변수를 나타낸다. 하지만 C#에서 더이상 사용되지 않기 때문에 당신은 이 키워드를
한번도 보지 못했을지도 모른다.

1
2
int variable = 10;
auto int variable = 10;

두 라인은 정확하게 같다.

automatic 변수의 특징은 콜 스택에 할당된다는 것이다. 
스택에서는 오직 맨윗부분에 변수를 추가하거나 제거할 수 있고, 맨 아랫 부분에는 변수를 추가하거나 삭제를 할 수가 없다.  이것은 접시 한무더기와 비슷하다. 밑에 그림을 봐라


가장 위에 있는 스택을 알기위해서 프로그램은 스택 포인터를 사용한다. 스택 포인터는 CPU안에 있으며, 위 그림에서 보이는 검은색 화살표에 의해서 나타나는 현재 위치를 추적한다. 변수를 추가할 때 마다 스택 포인터는 증가된다.(또는 자신이 사용하는 OS의 메모리 관리에 따라 감소될수도 있다.)

스택은 함수 호출을 위해 사용되며, 전체 프로그램은 다른 함수들을 차례로 호출하는 Update함수를 호출하는 메인 함수이다. 
그래서 당신의 스크립트안에 있는 변수는 다른 어떤 것으로 정의되어 있지않다면 stack에 저장되는
automatic 변수이다.

또한 스택 프레임(Stack frame)에 대한 개념을 만날 것이다. 간단히 말해서 이것은 현재 실행된 함수에 의해 지역적으로 할당된 변수들의 집합이다.(또한 return 주소값을 포함하는데, 이것은 해당 함수가 리턴되고 난후에 바로 다음에 실행될 명령 주소를 뜻한다.)
컴파일러는 스택 프레임의 끝을 가리키는 포인터를 유지하며, 실제로는 당신의 로컬 변수가 차지하는 메모리를 찾기위해 이 포인터로부터 -offest을 사용한다.
이것의 실제적인 결말은 함수가 리턴되고 난 후이다. 그리고 그 함수의 로컬 변수가 사용된 공간들중 어느 것도 사용되거나 할당되지 않는다. (다음 함수 호출을 위한 여분의 공간을 제외하고는)

만약 재귀 프로그램(함수가 자기자신을 호출하는) 작성한다면, 어쩌면 스택의 공간을 모두 써버릴수도 있다.
각 함수 호출은 모든 지역변수와 리턴 주소값이 스택에 존재하기 위한 공간을 요청한다.
만약 당신의 코드에 그들 스스로 멈추지 않고 실행되는 루틴이 존재한다면, 아마 스택 오버플로우 메시지를 보게 될 것이다.

그래서 생명주기와 automatic 변수의 범위는 서로 관련되어 있다. 변수는 중괄호{와 }안에서 존재한다.
이것은 설명이 필요하다.


01
02
03
04
05
06
07
08
09
10
11
void Update(){
    int number = 10;
    Fct(number);
    print(number);
}
 
void Fct(int n){
    int inside=20;
    n = n + inside;
    print(n);
}


업데이트는 호출되고 첫번째 명령어는 변수 선언이다. 스택포인터는  밀어올려지고, number의 값(10)은  메모리 장소에 위치한다.
두번째 명령어는 함수 호출이다. 함수를 호출할 때 프로그램은 멈추고, 함수의 주소로 이동하며,
프로그램의 원래의 위치로 되돌아 가기위한 약간의 데이터는 스택에 전달된다.
매개변수는 원래의 값을 복사하여 스택에 저장된다.
변수 number는 Fct함수에서 사용되기위해 스택에 저장되지 않고 number의 값을 복사한 새로운 변수 n이 만들어져 스택에 저장된다.
제 Fct함수안에는 number 변수의 값을 가지고 있는 변수 n을 가지고 있다. 그렇다고 Fct함수에 number함수가 존재하는 것은 아니기때문에 number변수에는 접근할 수가 없는 것이다.
함수안에서 새로운 변수 inside가  선언되었다. 스택은 다음과 같은 상태이다.(그림에서는 스택의 상황을 매우 단순화 시켰다는 것을 명심하라)




함수의 끝부분에, n은 출력되고 30의 값을 가진다. 함수 내부에서 만들어진 모든 변수는 파괴되고 사라진다. 그것은 n과 inside을 포함한다. 변수가 여전이 메
모리에 존재할지라도, 그들은 간단하게 무시되고 시스템에 의해 더 이상 고려되지 않는다.

Update함수로 돌아왔을 때, 원래 값 10을 가지는 number변수를 출력하는데, Fct함수에서 사용된 것은 number변수의 복사값이기 때문이다.
스택 포인터는 n과 inside의 위치를 잃어버리는데, 만약 Update함수로 돌아와서 다시 n과 inside함수에 접근하려고 한다면 컴파일러는 그 변수는 현재 지역에 존재하지 않는다는 에러메시지를 반환할 것이다.

우리는 automatic 변수는 범위는 중괄호{, }에의해 정의 된다는 것을 보았다. 그 밖에서는, 변수는 존재하지 않고 보이지 않는다. 중괄호는 함수, if문또는 반복문의 범위를 표시할 수 있다.

변수의 수명과 범위

수명은 프로그램안에서 변수가 존재하는 시간을 의미하며, 범위는 프로그램에서 변수가 보이는 지역을 정의한다. automatic 변수의 경우에는 수명과 범위가 유사하다.

값타입의 변수는 스택에 저장된다. 그들은 다음과 같다

  • int
  • unsigned
  • float
  • double
  • char
  • struct
  • bool
  • byte
  • enum
  • long
  • short
그 당시에 존재하지 않았던 bool형을 제외한 거의 모든 타입을 C로부터 상속받았다.

automatic 변수에 대해서, 그것의 생명은 compiler에 의해 관리된다. 선언된 범위의 끝은 변수의 끝을 표시한다. 프로그래머는 메모리 지역을 해제하는데 신경쓰지 않아도 된다. 그것은 자동으로 처리된다.




(다음 글에서 계속)


'유니티 개발 정보 > 개념' 카테고리의 다른 글

코루틴(Coroutine)++  (3) 2013.10.08
유니티 메모리 관리 - 4(마지막)  (0) 2013.10.03
유니티 메모리 관리 - 3  (1) 2013.08.13
유니티 메모리 관리 - 2  (0) 2013.08.09
유니티 코딩 규칙  (4) 2013.07.22