Unity/최적화
[Unity] Foreach의 GC의 원인 및 수정
EveryDay.DevUp
2021. 12. 29. 21:47
Unity 5.5 미만 버전에서 foreach를 사용할 경우 가비지가 발생하는 이슈가 있었다.
원인
- Mono C# Unity 버전에서 foreach loop 종료 시 값을 강제로 박싱
- 값 형식의 열거자를 생성하였는데, 해당 열거자를 사용할 경우 loop가 종료되는 시점에 IDisposable 인터페이스를 구현 해야 했음
- 인터페이스는 참조 형식이기 때문에, 값 형식을 인터페이스로 변환하는 중에 박싱이 발생하게 됨
참고 : https://everyday-devup.tistory.com/106
대응
- Unity의 C# 컴파일러가 최신 버전의 Mono 컴파일러로 업그레이드 되면서 수정 됨
List<int> intList = new List<int>{ 1, 2, 3 };
foreach( int i in intList) {}
// foreach IL GetEnumerator
[__DynamicallyInvokable]
public List<T>.Enumerator GetEnumerator() => new List<T>.Enumerator(this);
// valueType의 eumerator는 IDisposable interface를 구현
public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator
public interface IDisposable
{
[__DynamicallyInvokable]
void Dispose();
}
// Unity 버전 5.5 미만에서는
finally
{
IL_0030: ldloc.2 // V_2
// valueType에서 interface (참조형식) 박싱을 해서 Dispose를 호출
IL_0031: box valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0036: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003b: endfinally
}
// Unity 버전 5.5 이상에서는
finally
{
IL_0035: ldloca.s V_0
// 박싱이 발생하지 않음, ValueType List<T>.Enumerator의 포인터를 직접 넘겨주게 변경 됨.
// C# constrained접두사는 callvirt this Type 이 값 형식 인지 또는 참조 형식 인지에
// 관계 없이 일관 된 방식으로 명령을 수행할 수 있도록 설계 되었습니다.
IL_0037: constrained. valuetype [netstandard]System.Collections.Generic.List`1/Enumerator<int32>
IL_003d: callvirt instance void [netstandard]System.IDisposable::Dispose()
IL_0042: endfinally
}
* 출처 및 참고 블로그
https://docs.unity3d.com/kr/2019.4/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html
https://docs.microsoft.com/ko-kr/dotnet/api/system.reflection.emit.opcodes.constrained?view=net-6.0