1-1. 위의 코드가 문제가 되는 이유를 메모리 관점에서 설명해주세요.
ⓐ 문자열의 불변성
- C#에서 문자열은 불변(immutable)이다. 즉, 한 번 생성된 문자열은 변경될 수 없다.
문자열에 새로운 내용을 추가할 때마다 새로운 문자열 객체가 생성된다. - LogMessages에 메시지를 추가할 때마다, 새로운 문자열 객체가 생성되고 기존 문자열의 내용과 새로 추가된 문자열의 내용이 모두 복사된다. 이는 메모리 사용을 비효율적으로 만든다.
ⓑ 메모리 재할당과 복사
- LogMessages가 길어질수록 새 문자열을 만들 때 필요한 메모리도 늘어나게 된다. 기존 문자열이 1000자의 길이를 가지고 있다면, 새 문자열을 생성하기 위해 기존 1000자와 새로 추가될 문자열의 길이만큼의 메모리가 필요하다.
- 이는 반복될수록 더 많은 메모리가 할당되고, 기존 문자열을 새 문자열로 복사하는 작업이 반복되므로 성능이 저하된다.
ⓒ GC(가비지 컬렉션) 오버헤드
- 메모리 재할당과 복사가 반복되면서 많은 임시 문자열 객체가 생성되고 소멸된다.
이러한 객체들은 가비지 컬렉터에 의해 회수되어야 하므로, 가비지 컬렉션의 빈도가 증가하게 된다.
이는 프로그램의 성능에 부정적인 영향을 미칠 수 있다.
ⓓ 스케일링 문제
- 예제 코드에서는 10,000번의 로그 메시지를 추가하고 있다. 실제 애플리케이션에서는 이보다 더 많은 로그가 필요할 수 있다. 이 경우 위와 같은 메모리 재할당 문제는 더 심각해질 수 있다.
이를 개선하기 위해서는 StringBuilder를 사용하는 것이 좋다.
StringBuilder는 가변(mutable) 문자열을 사용하므로, 문자열을 추가할 때마다 새로운 객체를 생성하지 않고 기존 버퍼에 문자를 추가하기 때문에 메모리 사용이 효율적이다.
2-1. 가비지 컬렉터란 무엇인가요?
가비지 컬렉터(Garbage Collector, GC)는 프로그램에서 더 이상 사용되지 않는 메모리(객체)를 자동으로 회수하여 메모리 누수를 방지하고 효율적인 메모리 관리를 도와주는 시스템이다. C#을 포함한 여러 현대 언어들(C#, Java 등)은 자동 메모리 관리를 위해 가비지 컬렉터를 사용한다. GC는 메모리를 정기적으로 검사하여 참조되지 않는 객체를 찾아내고 이를 회수하여 메모리 풀이 다시 사용될 수 있게 한다.
2-2. 가비지 컬렉터의 장점과 단점에 대해 설명해주세요.
장점
- 자동 메모리 관리:
- 프로그래머가 직접 메모리를 해제하지 않아도 되므로 메모리 관리가 용이해진다.
- 메모리 누수 방지:
- 사용되지 않는 메모리를 자동으로 회수하여 메모리 누수를 방지한다.
- 안정성 향상:
- 잘못된 메모리 접근(예: 이중 해제, 해제 후 접근 등)으로 인한 버그를 줄일 수 있다.
단점
- 성능 오버헤드:
- 가비지 컬렉션 작업은 프로그램 실행 중에 발생하므로, 일시적인 성능 저하를 초래할 수 있다.
- 예측 불가능한 타이밍:
- GC는 언제든지 작동할 수 있으므로, 실시간 시스템에서 예측하지 못한 타이밍에 성능 저하가 발생할 수 있다.
- 메모리 사용 증가:
- GC가 작동하기 전까지는 사용되지 않는 객체들이 메모리를 차지하고 있을 수 있다.
2-3. 가비지 컬렉터의 세대 개념에 대해 설명해주세요.
가비지 컬렉터는 효율성을 높이기 위해 객체를 세대(generation)로 분류하여 관리한다.
C#의 GC는 일반적으로 세 가지 세대를 사용한다.
세대 0 (Generation 0):
- 가장 최근에 생성된 객체들이 위치합니다. 객체의 생존 시간이 짧을 것이라고 가정한다.
- 세대 0의 가비지 컬렉션이 가장 빈번하게 일어난다.
세대 1 (Generation 1):
- 세대 0에서 살아남은 객체들이 승격되는 세대입니다. 중간 정도의 생존 시간을 가진 객체들이 위치한다.
세대 2 (Generation 2):
- 세대 1에서 살아남은 객체들이 승격되는 세대입니다. 장기 생존 객체들이 위치한다.
- 가장 드물게 가비지 컬렉션이 일어난다.
세대 개념을 사용하면 단기 생존 객체와 장기 생존 객체를 구분하여 가비지 컬렉션의 빈도와 비용을 최적화할 수 있다.
2-4. 박싱, 언박싱을 사용할 때 주의해야 할 점은 무엇일까요?
박싱(Boxing):
- 값 타입(예: int, bool 등)을 힙에 할당된 객체로 변환하는 과정
- 박싱이 발생하면 값 타입이 객체로 변환되어 힙에 저장되고, 메모리 할당 및 복사 비용이 발생
언박싱(Unboxing):
- 힙에 할당된 객체를 다시 값 타입으로 변환하는 과정
- 언박싱 과정에서는 힙에서 값을 읽어와 스택에 복사하는 작업이 필요
주의해야 할 점:
성능 문제
- 박싱과 언박싱은 메모리 할당 및 복사가 필요하기 때문에 성능 오버헤드가 발생할 수 있다.
따라서 빈번한 박싱과 언박싱을 피하는 것이 좋다.
명시적 변환
- 언박싱 시에는 명시적인 타입 변환이 필요하므로,
잘못된 타입으로 언박싱하려고 하면 InvalidCastException이 발생할 수 있다.
불필요한 박싱 방지
- 컬렉션 사용 시, 제네릭을 활용하여 박싱을 피할 수 있다.
예를 들어, List<int>를 사용하면 박싱이 발생하지 않지만, ArrayList를 사용하면 박싱이 발생할 수 있다.
2-5. 오브젝트 풀을 사용하면 메모리 관리에 도움이 되는 이유가 무엇일까요?
오브젝트 풀(Object Pool)은 객체를 생성하고 재사용할 수 있도록 하는 디자인 패턴이다.
필요한 객체를 새로 생성하지 않고, 미리 생성된 객체를 재사용함으로써 메모리 사용과 성능을 최적화할 수 있다.
- 객체 생성 비용 감소:
- 객체를 반복적으로 생성하고 소멸하는 대신, 재사용 가능한 객체를 유지함으로써 객체 생성 및 소멸에 따른 비용을 줄일 수 있다.
- 메모리 할당 감소:
- 힙 메모리 할당이 줄어들어 메모리 단편화와 가비지 컬렉션 횟수를 줄일 수 있다.
- 성능 향상:
- 재사용 가능한 객체를 제공함으로써 응답 시간을 단축하고 애플리케이션의 전반적인 성능을 향상시킬 수 있다.
- 안정성 증가:
- 객체 생성 실패(예: 메모리 부족)에 대한 위험을 줄이고, 안정적인 객체 할당을 보장할 수 있다.
오브젝트 풀은 특히 빈번하게 생성되고 소멸되는 객체가 있는 경우에 유용하다.
예를 들어, 네트워크 연결, 스레드, 데이터베이스 연결과 같은 자원은 오브젝트 풀을 통해 효율적으로 관리할 수 있다.
'C# > Knowledge' 카테고리의 다른 글
선형 자료 구조 - LinkedList, Stack, QueQue (0) | 2024.07.16 |
---|---|
C# 심화 문법 (0) | 2024.07.11 |
상속과 인터페이스 (0) | 2024.07.09 |
스택 메모리 vs 힙 메모리 (0) | 2024.07.09 |
콜백, delegate, event (0) | 2024.07.04 |