이벤트 함수의 실행 순서
플랫폼 의존 컴파일

자동 메모리 관리를 이해하기

When an object, string or array is created, the memory required to store it is allocated from a central pool called the heap. When the item is no longer in use, the memory it once occupied can be reclaimed and used for something else. In the past, it was typically up to the programmer to allocate and release these blocks of heap memory explicitly with the appropriate function calls. Nowadays, runtime systems like Unity’s Mono engine manage memory for you automatically. Automatic memory management requires less coding effort than explicit allocation/release and greatly reduces the potential for memory leakage (the situation where memory is allocated but never subsequently released).

값 형식과 참조 형식

함수가 호출되면 인수의 값은 특정 호출을 위해 예약된 메모리 영역에 복사됩니다. 몇 바이트를 차지하는 데이터 형식은 신속하고 간단하게 복사할 수 있습니다. 그러나, 오브젝트, 문자열, 배열이 그것보다 큰 것은 자주 있는 일이며, 이러한 데이터 형식을 정기적으로 복사하는 것은 매우 비효율적입니다. 다행히 이것은 필요하지 않습니다. 큰 항목에 대한 실제 저장 공간은 힙에서 할당되어, 작은 “포인터” 값이 위치를 기억하기 위하여 존재합니다. 그 다음은 포인터만이 파라미터 전달시 필요합니다. 런타임 시스템이 포인터에 의해 식별되는 항목을 찾아내는 한, 데이터의 하나의 사본은 여러 번 사용할 수 있습니다.

Types that are stored directly and copied during parameter passing are called value types. These include integers, floats, booleans and Unity’s struct types (eg, Color and Vector3). Types that are allocated on the heap and then accessed via a pointer are called reference types, since the value stored in the variable merely “refers” to the real data. Examples of reference types include objects, strings and arrays.

메모리 할당 및 가비지 컬렉션

메모리 관리자가 힙에서 사용되지 않는 것으로 인식하고 있는 영역을 추적합니다. 새로운 메모리 블록이 요구될 때(예를 들어 오브젝트가 인스턴스화 된), 관리자는 블록을 할당되지 않은 공간을 선택하고 사용하지 않는 것으로 인식하고 있는 할당된 메모리를 제거합니다. 이후 요청은 요청된 블록 크기를 할당하는 데 충분한 공간이 없을 때까지 동일한 방식으로 처리됩니다. 이 시점에서 힙에 할당된 모든 메모리가 사용되는 경우는 극히 드뭅니다. 힙에서 참조되는 항목은 아직 그것을 참조할 수 있는 참조 변수가 존재하는 경우에만 액세스 할 수 있습니다. 만약 메모리를 참조하는 모든 메모리 블록이 없어 졌을 때(즉 참조 변수가 다시 할당되었는지, 범위에서 제외된 로컬 변수인 경우)는 점유하고 있던 메모리는 안전하게 재할당할 수 있습니다.

어떤 힙 블록이 더 이상 사용되지 않는지 확인하기 위해 메모리 관리자는 모든 현재 활성화된 참조 변수를 검색하여 블록을 “live”로 표시합니다. 검색 마지막에 활동중인 블록 사이의 공간은 메모리 관리자에 의해 비어있다고 판단되어, 할당에 사용할 수 있습니다. 이러한 이유로, 사용하지 않는 메모리 처리와 해제를 가비지컬렉션, 줄여서 GC라고합니다.

최적화

가비지 컬렉션은 자동이며, 프로그래머에게 보이지 않지만 수집 과정은 실제로는 상당한 CPU 시간을 필요로 합니다. 올바로 사용되면 자동 메모리 관리는 일반적으로 수동 할당과 동등 또는 그 이상의 전체 퍼포먼스를 얻을 수 있습니다. 하지만 컬렉터를 필요 이상으로 트리거하여, 실행중에 멈추는 것을 피하는 것이 프로그래머에게 중요합니다.

There are some infamous algorithms that can be GC nightmares even though they seem innocent at first sight. Repeated string concatenation is a classic example:

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void ConcatExample(int[] intArray) {
        string line = intArray[0].ToString();
        
        for (i = 1; i < intArray.Length; i++) {
            line += ", " + intArray[i].ToString();
        }
        
        return line;
    }
}


//JS script example
function ConcatExample(intArray: int[]) {
    var line = intArray[0].ToString();
    
    for (i = 1; i < intArray.Length; i++) {
        line += ", " + intArray[i].ToString();
    }
    
    return line;
}

여기서 핵심 포인트는 새로운 부분이 문자열에 하나하나 추가되지 않는다는 것입니다. 실제로 루프 때마다 행해지는 것은, line 변수의 이전 내용이 사라집니다 - 원래의 부분에 새로운 부분을 더한 완전히 새로운 문자열이 저장됩니다. 문자열은 1 씩 값이 증가되기 때문에 소비되는 힙의 양 또한 증가하고 이 함수가 호출될 때마다 수백 바이트의 여유 힙 공간을 쉽게 차지합니다. 만약 많은 문자열을 연결할 때, Mono 라이브러리의 System.Text.StringBuilder 클래스를 사용한 보다 좋은 예가 있습니다.

However, even repeated concatenation won’t cause too much trouble unless it is called frequently, and in Unity that usually implies the frame update. Something like:

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public GUIText scoreBoard;
    public int score;
    
    void Update() {
        string scoreText = "Score: " + score.ToString();
        scoreBoard.text = scoreText;
    }
}


//JS script example
var scoreBoard: GUIText;
var score: int;

function Update() {
    var scoreText: String = "Score: " + score.ToString();
    scoreBoard.text = scoreText;
}

…will allocate new strings each time Update is called and generate a constant trickle of new garbage. Most of that can be saved by updating the text only when the score changes:

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    public GUIText scoreBoard;
    public string scoreText;
    public int score;
    public int oldScore;
    
    void Update() {
        if (score != oldScore) {
            scoreText = "Score: " + score.ToString();
            scoreBoard.text = scoreText;
            oldScore = score;
        }
    }
}


//JS script example
var scoreBoard: GUIText;
var scoreText: String;
var score: int;
var oldScore: int;

function Update() {
    if (score != oldScore) {
        scoreText = "Score: " + score.ToString();
        scoreBoard.text = scoreText;
        oldScore = score;
    }
}

Another potential problem occurs when a function returns an array value:

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    float[] RandomList(int numElements) {
        var result = new float[numElements];
        
        for (int i = 0; i < numElements; i++) {
            result[i] = Random.value;
        }
        
        return result;
    }
}


//JS script example
function RandomList(numElements: int) {
    var result = new float[numElements];
    
    for (i = 0; i < numElements; i++) {
        result[i] = Random.value;
    }
    
    return result;
}

This type of function is very elegant and convenient when creating a new array filled with values. However, if it is called repeatedly then fresh memory will be allocated each time. Since arrays can be very large, the free heap space could get used up rapidly, resulting in frequent garbage collections. One way to avoid this problem is to make use of the fact that an array is a reference type. An array passed into a function as a parameter can be modified within that function and the results will remain after the function returns. A function like the one above can often be replaced with something like:

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void RandomList(float[] arrayToFill) {
        for (int i = 0; i < arrayToFill.Length; i++) {
            arrayToFill[i] = Random.value;
        }
    }
}


//JS script example
function RandomList(arrayToFill: float[]) {
    for (i = 0; i < arrayToFill.Length; i++) {
        arrayToFill[i] = Random.value;
    }
}

이는 단순히 기존 배열의 내용을 새 값으로 대체합니다. 따라서 초기 할당이 호출하는 원래 코드에서 행해지는 것이 필요해짐에도 불구하고(약간 우아하지 않아 보이지만), 함수는 호출될 때 새로운 가비지를 생성하는 것은 없어집니다.

가비지 수집 요청

앞서 언급했듯이, 가능한 한 할당을 피하는 것이 좋습니다. 하지만 완전히 배제할 수 없는 것을 생각하면, 게임 플레이에 침입을 최소화하는데 사용 할수 있는 두가지 전략이 있습니다.

작은 Heap 과 빠르고 빈번한 가비지 컬렉션

This strategy is often best for games that have long periods of gameplay where a smooth framerate is the main concern. A game like this will typically allocate small blocks frequently but these blocks will be in use only briefly. The typical heap size when using this strategy on iOS is about 200KB and garbage collection will take about 5ms on an iPhone 3G. If the heap increases to 1MB, the collection will take about 7ms. It can therefore be advantageous sometimes to request a garbage collection at a regular frame interval. This will generally make collections happen more often than strictly necessary but they will be processed quickly and with minimal effect on gameplay:

if (Time.frameCount % 30 == 0)
{
   System.GC.Collect();
}

그러나 이 기술은 주의해서 사용되어야 하며, 프로파일러 통계를 확인하고 정말 자신의 게임 컬렉션 시간을 줄이고 있는지 확인해야 합니다.

큰 Heap과 느리지만 빈번하지 않은 가비지 컬렉션

This strategy works best for games where allocations (and therefore collections) are relatively infrequent and can be handled during pauses in gameplay. It is useful for the heap to be as large as possible without being so large as to get your app killed by the OS due to low system memory. However, the Mono runtime avoids expanding the heap automatically if at all possible. You can expand the heap manually by preallocating some placeholder space during startup (ie, you instantiate a “useless” object that is allocated purely for its effect on the memory manager):

//C# script example
using UnityEngine;
using System.Collections;

public class ExampleScript : MonoBehaviour {
    void Start() {
        var tmp = new System.Object[1024];
        
        // make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks
        for (int i = 0; i < 1024; i++)
            tmp[i] = new byte[1024];
        
        // release reference
        tmp = null;
    }
}


//JS script example
function Start() {
    var tmp = new System.Object[1024];

    // make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks
        for (var i : int = 0; i < 1024; i++)
        tmp[i] = new byte[1024];

    // release reference
        tmp = null;
}

A sufficiently large heap should not get completely filled up between those pauses in gameplay that would accommodate a collection. When such a pause occurs, you can request a collection explicitly:

System.GC.Collect();

다시 말하면, 이 전략을 사용할 때, 프로파일러 통계를 확인하여, 정말 효과가 있는지 제대로 확인해야 합니다.

재사용 가능한 오브젝트 풀링

생성 및 소멸되는 오브젝트 수를 줄이는 것만으로 가비지를 회피할 수 있는 경우가 자주 있습니다. 게임 속에서 발사물과 같은 특정 오브젝트처럼 한 번에 적은 수만 필요한데도 불구하고 반복 발생하는 케이스가 있습니다. 이러한 경우에는 예전 것을 버리고 새 것으로 교체하는 것이 아니라, 오브젝트를 재사용할 수 있습니다.

상세 정보

메모리 관리는 교묘하고 복잡한 주제이며, 지식을 습득하는 노력도 큽니다. 만약 더 많은 것을 배우고 싶다면 memorymanagement.org가 좋은 정보를 가지고 있으며, 출판물과 온라인 기사 목록이 있습니다. 오브젝트 풀링에 대한 자세한 내용은 Wikipedia 페이지Sourcemaking.com에 포함됩니다.

이벤트 함수의 실행 순서
플랫폼 의존 컴파일