Closure
- Closure는 예제로 배우는 C# 프로그래밍에 따르면 다음과 같이 설명 되어있습니다.
무명메서드나 람다식이 그것을 정의하고 있는 메서드의 로컬변수를 사용하고 있을 때 Closure라고 하며 C# 2.0부터 지원합니다. Closure는 로컬변수를 갖는 Nested 클래스를 만들고 객체로 관리 됩니다. 다음 예제를 보면 Closure의 작동 방식을 이해 할 수 있습니다.
class MyClass
{
public void DoTest()
{
Action action = GetAction();
action();
action();
}
private Action GetAction()
{
int a = 1;
Action act = () =>
{
Console.WriteLine(a);
a++;
};
return act;
}
}
- 일반 메서드의 지역변수는 스택에 쌓이고 메서드 종료 시점에서 제거되지만 Closure는 Nested 클래스를 생성하고 객체로 관리되므로 힙에 할당되기 때문에 a값이 증가하며 DoTest가 종료되는 시점에서 GC로 관리되게 됩니다. 결국 Closure는 의도치 않게 가비지가 쌓이게 되므로 성능을 생각하면 피하는 것이 좋습니다.
박싱
- 박싱은 주로 값 타입이 레퍼런스 타입으로 활용될 때마다 발생되며 의도하지 않은 임시 메모리 할당이 발생하는 가장 주된 원인입니다.
1. 값 타입
int x = 1;
object y = new object();
y.Equals(x);
2. Dictionaries, enums
- 딕셔너리의 키로 enum을 사용하는 경우 Object.getHashCode(Object)가 호출 되므로 박싱이 발생합니다. 이를 해결하기 위해 Dictionary를 생성 할 때 다음과 같은 비교자를 할당해줍니다.
public class MyEnumComparer : IEqualityComparer<MyEnum>
{
public bool Equals(MyEnum x, MyEnum y)
{
return x == y;
}
public int GetHashCode(MyEnum x)
{
return (int)x;
}
}
Foreach 루프
- Mono C# 컴파일러의 Unity 버전에서는 foreach루프를 사용하는 경우 각각의 루프가 종료되는 시점에 IDisposable인터페이스를 구현하여 호출됩니다. 그러므로 값 형식의 오브젝트에서 인터페이스 메서드를 호출하기 위해 박싱해야 합니다. 또한 반복 메서드 호출비용이 크기 때문에 속도에서도 큰차이를 보여 Unity에서 피하는 것을 권장합니다.
Unity 5.5버전에서 C#컴파일러가 업그레이드되어, IL을 생성하는 기능이 크게 개선되었으며 박싱작업도 제거 되었지만 여전히 Array기반 코드에서 CPU성능 차이를 보입니다. 그러므로 Array기반에서는 성능 이슈가 있는 부분이라면 for문을, 그 외에는 foreach를 쓰는 것을 권장합니다. For문과 Update같은 경우 성능과 밀접한 연관이 되어 있으므로 항상 주의해서 사용합시다.
배열 기반 Unity API
- 프로퍼티를 한 번 액세스하는 CPU비용은 그렇게 크지는 않지만, 루프에서 계속해서 액세스하면 CPU성능을 크게 차지 합니다. 그러므로 루프에 들어가기 전에 할당하여 사용합니다.
var vertices = mesh.vertices;
for(int i = 0; i < vertices.Length; i++)
{
float x, y, z;
x = vertices[i].x;
y = vertices[i].y;
z = vertices[i].z;
DoSomthing(x, y, z);
}
- 메모리 할당을 유발하지 않는 Unity Api를 활용합니다.
for ( int i = 0; i < Input.touches.Length; i++ )
{
Touch touch = Input.touches[i];
}
int touchCount = Input.touchCount;
for ( int i = 0; i < touchCount; i++ )
{
Touch touch = Input.GetTouch(i);
}