[C#] 박싱과 언박싱 (Boxing & UnBoxing)

Donghee·2024년 4월 3일
0

개요

Boxing 및 unboxing은 계산을 많이 해야 하는 프로세스입니다. 값 형식이 boxing되면 완전히 새로운 개체가 생성되어야 합니다. 이 작업은 단순 참조 할당보다 20배나 오래 걸립니다. unboxing 시 캐스팅 프로세스는 할당의 4배에 달하는 시간이 소요될 수 있습니다.

마이크로소프트 .NET 성능 팁에 적힌 문구이다.
박싱과 언박싱은 무엇이길래 4배, 20배까지 단순 할당보다 오래 걸리는 작업이라고 하는 것일까?

박싱과 언박싱

C# 언어에서 값 형식으로 되어있는 값을 참조 형식의 값으로 변환하는 작업을 박싱이라고 한다. 그리고 반대로 그 박싱했던 참조 형식의 값을 다시 원래대로 돌리는 것을 언박싱이라고 한다.
이것은 모두 클래스 계층 구조의 최상위에 있는 Object를 통해 이루어진다. 즉, 박싱은 값 형식이 Object 형태로 변환되는 것이나 마찬가지인 것이다.
마이크로소프트에서 제공하는 예를 통해 어떤 때에 박싱/언박싱이 이루어지는지 살펴보자.

int i = 123;
// i를 박싱 -> object 형식의 o에 할당
object o = i;

여기서 i는 값 형식인 상태이다. 이 값을 박싱하고 o에 저장하는 모습이다.
형변환에서 하위 형이 상위 형으로 변환될 때는 암시적으로 변환되는 것처럼, 박싱은 암시적으로 이루어진다.

o = 123;
int j = (int)o;  // o를 명시적으로 언박싱

언박싱은 상위 형에서 하위 형으로 변환되는 형변환처럼, 명시적으로 이루어져야 한다.
진행되는 코드는 형변환과 유사해 보인다. 하지만 어떤 이유로 단순 할당보다 성능이 그렇게 떨어지는 것일까? 이 이유를 알기 위해서는 C#에서 값 형식과 참조 형식이 저장되는 과정을 알아야 한다.

값 형식과 참조 형식의 저장

C#에서 값 형식은 스택 메모리에, 참조 형식은 힙 메모리에 값을 저장한다. 정확히는 힙 메모리에 값을 저장하고, 스택 메모리에 힙 메모리의 주소를 저장한다. 그리고 참조 형식 개체는 그 주소를 가지고 있는 것이다. 기존의 클래스 인스턴스를 새로운 인스턴스에게 할당해줬을 때, 둘이 같은 객체를 가리키고 있는 것은 이것 때문이다.
이 사실을 바탕으로 박싱/언박싱을 다시 바라보자.

박싱/언박싱의 성능

박싱

박싱은 값 형식을 힙 메모리에 저장시킨다. 정확히는 힙 메모리에 새 개체가 할당되고 값이 이것에 복사된다.

이 사진에서 보는 것처럼 i가 박싱된 개체가 힙 메모리에 저장되고, 이 개체를 o가 가리키고 있는 모양새다. 이 과정을 풀어서 말해보면,
1. 힙 메모리에 새 개체 생성
2. 스택 메모리에 있는 값 복사
3. 스택 메모리에 이 개체의 주소 값을 가진 새로운 메모리 할당
위와 같다. 힙 메모리, 스택 메모리에 모두 할당을 하고 메모리 참조가 여러번 이루어지다 보니, 단순 참조 할당보다 최대 20배의 시간이 걸리기도 한다.

언박싱

언박싱은 참조 형식(Object 형식)에서 값 형식으로 변환시킨다. 이 과정 또한 풀어서 말해보면,
1. 개체가 지정한 값 형식을 박싱한 값인지 확인
2. 개체의 값을 값 형식 변수에 복사
즉, 힙 메모리에 있는 값을 확인해 명시한 값 형식이 호환되는지 확인하고, 이를 복사해오는 것이다. 이것 또한 평범한 할당보다는 복잡하므로, 최대 4배의 시간이 걸린다고 한다.

결론

마이크로소프트 .NET 성능 팁에서는 ArrayList처럼 제네릭이 아니기 때문에 박싱이 많이 이루어지는 곳에서는 값 형식을 사용하지 않는 것이 좋다고 조언한다. 차라리 List와 같이 제네릭 컬렉션을 활용하면 값 형식을 박싱할 필요가 없으니, 이것이 성능에 훨씬 도움이 된다고 한다.

참고

profile
마포고개발짱

0개의 댓글