velog 개설 이후 처음쓰는 지식정리 글이라 어떻게 써야할지 막막하군요 ㅎㅎ
무엇보다 목표는 오랜시간 뒤 제가 알아볼 수 있고, 기억하기 위한 정리이니 최대한 간단하면서도 필요한 내용만 기술하며 작성하려합니다.
꾸준히 하다보면 어느순간 노하우가 생길터이니 고민하지 않고 써보겠습니다.
저번 기술면접 때 다양한 CS질문들이 등장했었는데, 그 중 Boxing과 Unboxing에 대해 정리해보도록 하겠습니다.
MSDN에는 Boxing, Unboxing에 대해 다음과 같이 명시되어 있습니다.
Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type.
When the common language runtime (CLR) boxes a value type, it wraps the value inside a System.Object instance and stores it on the managed heap.
Unboxing extracts the value type from the object. Boxing is implicit; unboxing is explicit.
The concept of boxing and unboxing underlies the C# unified view of the type system in which a value of any type can be treated as an object.MSDN 참고 (https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing)
위 구문을 통해 Boxing, Unboxing이 무엇인지 정의할 수 있고 각 특징을 알 수 있습니다.
Boxing
ValueType을 ReferenceType으로 변환하는 과정
- 묵시적이다.
- CLR(Common Language Runtime)에서 Boxing 시 System.Object 내에 존재하게될 값을 래핑하여 Heap 메모리에 보관한다.
Unboxing
object에서 값을 추출하는 과정
- 명시적이다.
코드에서 Boxing, UnBoxing은 다음과 같은 상황에 발생합니다.
int a = 1;
// Boxing 발생
object o = a;
// Unboxing
int b = (int)o;
위 예시에서 알 수 있는 점은 다음과 같습니다.
- int와 같은 ValueType을 object와 같은 ReferenceType으로 변환할 때 Boxing이 발생.
- ReferenceType인 object에서 ValueType인 int로 변환할 때 Unboxing이 발생.
- Boxing 시에는 따로 표기 없이 object 변수에 대입. (묵시적)
- Unboxing 시에는 캐스팅 형태를 통해 변환타입을 표기. (명시적)
그렇다면 어떤 과정을 통해 Boxing이 이루어지는 걸까요 ?
다음은 MSDN에서 제공하는 Boxing, Unboxing에 대한 예제와 이미지입니다.
int i = 123;
object o = i;
int j = (int)o;
먼저 ValueType과 ReferenceType 각각 다음과 같은 방식으로 메모리를 사용합니다.
- ValueType은 Stack 메모리 해당 값 자체를 저장
- ReferenceType은 Heap 메모리에 값을 저장하고 해당 주소를 Stack 메모리에 저장
그렇다면 Boxing과 Unboxing은 Stack 메모리에 있던 값을 Heap 메모리로 복사하거나, Heap 메모리에 있던 값을 Stack 메모리에 복사하는 등의 공정이 필요하겠죠.
따라서 Boxing, Unboxing이 실행될 때는 다음과 같은 내부과정을 거칩니다.
Boxing
- Heap 영역에 필요한 만큼 메모리 할당
- Boxing할 대상 값(ValueType)을 할당한 Heap 영역에 복사
- 해당 Heap 영역 주소를 Stack 메모리에 저장
Unboxing
- Stack 메모리에 영역 할당
- 해당 영역에 Heap 메모리에 있던 값 지정
Boxing, Unboxing은 다음과 같은 요인으로 인해 성능에 영향을 미칠 수 있습니다.
- 내부과정 중 메모리 할당, 값 복사, 캐스팅하는 과정에서의 비용
- Boxing 때 사용한 1회성 object들로 인해 발생하는 Garbage
아래 코드로 테스트 해봅시다.
static void Main(string[] args)
{
var a = 123;
var boxList = new ArrayList();
var list = new List<int>();
// Boxing, Unboxing이 발생하지 않을 때
Test("Boxing이 발생할 때\t\t", () =>
{
for (int i = 0; i < 10000000; i++)
boxList.Add(a);
});
// Boxing, Unboxing이 발생할 때
Test("Boxing이 발생하지 않을 때\t", () =>
{
for (int i = 0; i < 10000000; i++)
list.Add(a);
});
}
private static void Test(string resultPrefix, Action action)
{
var start = DateTime.Now;
action.Invoke();
Console.WriteLine($"{resultPrefix}: {(DateTime.Now - start).TotalSeconds}");
}
결과를 보니 생각보다 속도차이가 있군요.
1000만 번의 boxing 차이 인데 17배 정도 차이가 납니다.
마지막으로 나중에 누군가에게 간단하게 설명할 수 있도록 중요 포인트만 정리하면
- Boxing : ValueType을 ReferenceType으로 변환하는 것을 의미
- Unboxing : ReferenceType을 ValueType으로 변환하는 것을 의미
- Boxing/Unboxing이 실행되면서 Casting 비용, Garbage 발생과 같은 비용이 발생되고 이는 SW 성능에 영향을 미칠 수 있음.
- 따라서, 개발 시 Boxing/Unboxing의 성능 저하 여부를 생각하며 개발 필요.
이 정도가 될 거 같네요.
감사합니다.
이전 글을 보다 이 글까지 보게 되었는데, 성능까지 비교해주셔서 확실히 차이가 있다는 걸 알고 가네요! 감사합니다.