CLR C#과 Unity C#의 차이

김동현·2022년 8월 12일
1

C#

  • 마이크로소프트에서 개발한 완전 객체 지향 프로그래밍 언어
  • 자바를 본떠서 만들었다.
  • 무조건 클래스가 있어야한다.
  • 강건하고 유지보수를 위한 여러가지 기능을 제공한다
    • 가비지 컬렉션 : 메모리를 자동으로 정리
    • 람다 식 : 함수형 프로그래밍을 위한 것
    • 비동기 프로그래밍

.NET 아키텍처

마이크로소프트는 어떤 플랫폼이던지 언어를 동작시킬 수 있도록 공용 언어 인프라(CLI : Common Language Infrastructure) 라는 걸 발표함

.NET : 이 사양에 맞춰 마이크로소프트가 구현한 프로그램인 공용 언어 런타임(CLR : Common Language Runtime)과 클래스 라이브러리 세트를 망한다.

운영체제 위 중간단계 프로그램 위 C#프로그램 3계층이다

중간단계의 프로그램위에서 동작하는 프로그래밍 언어중 하나
자바 : JBM
C# : .NET 인것이다

빌드

C#은 컴파일을 하면 CLI 사양을 준수하는 중간 언어(Intermediate Language)로 컴파일 된다. 그리고 이러한 IL 코드와 프로그램에 사용되는 리소스가 함께 패키징 되어 어셈블리(Assembly)가 된다.

  • 리소스 : 아이콘, 마우스커서, 메뉴 등
  • 여기서 어셈블리는 우리가 흔히 알고있는 어셉블리랑 다르다.

어셈블리는 서로 함께 사용되어 논리적 기능 단위를 형성하도록 빌드되는 타입 및 리소스의 컬렉션을 의미한다.
어셈블리는 실행 파일(.exe) 또는 동적 연결 라이브러리(.dll)의 형태를 가지며, .NET 기반 애플리케이션에 대한 배포, 버전 제어, 재사용, 활성화 범위 및 보안 권한의 기본 단위를 형성한다.

C# 프로그램을 실행하면 어셈블리가 CLR에 로드 되는데, CLR은 IL 코드를 플랫폼에 따라 JIT(Just-In-Time) 컴파일 혹은 AOT(Ahead-Of-Time) 컴파일을 수행하여 네이티브 명령어로 변환한다.

  • JIT : 프로그램 실행 중에 그때그때 컴파일을 하는 것이다.
  • AOT : 프로그램 실행 전 미리 컴파일을 진행하는 것이다.

공용 타입 시스템

.NET에서는 여러 .NET 언어를 지원하기 위해 공용 타입 시스템(CTS : Common Type System) 을 지원한다.
.NET의 모든 형식은 값 타입(Value Type) 혹은 참조 타입(Reference Type)으로 구분된다.
모든 타입은 기본타입인 System.Object 에서 파생된다.

값 타입

C#에서 값 타입은 구조체 / 열거형 / 그외에 기본제공 타입으로 구성된다.

  • 구조체를 제외한 모든 타입은 System.ValueType에서 파생된다.
  • 스택 메모리에 직업 값이 포함된다. 다시말해 복사가 일어난다.
  • 상속이 불가능하다.
  • 구조체 멤버 중에 참조 타입이 있다면 메모리 주소가 복사된다.(얕은 복사)

참조 타입

C#에서 참조 타입은 클래스 / 대리자 / 배열 / 인터페이스 가 있다.

  • 힙 메모리에 인스턴스가 할당된다.
  • 참조 타입의 변수는 인스턴스의 주소에 대한 참조를 가진다.
  • Null을 할당할 수 있다.

박싱과 언박싱

박싱 : 값 타임을 object타입 또는 값 타입에서 구현된 임의의 인터페이스 타입으로 변환하는 프로세스
스텍에서 힙으로

int i = 10;
object o = i;
값 타입을 object 타입으로 변환할 때 일어나는 것을 박싱이라 한다.

언박싱 : 박싱된 인스턴스에서 값 타입을 추출하는 프로세스
힙에서 스텍으로

데이터 추출하는 것을 언박싱이라 한다.

박싱은 암시적으로 이뤄지며, 언박싱은 명시적으로 이뤄진다.

가급적 박싱과 언박싱을 쓰지 않도록 하자

가비지 컬렉션

C++ 에서는 메모리를 프로그래머가 관리한다.
즉, 동적 할당된 메모리를 전부 사용했다면 시스템에 돌려줘야 한다.

  • 메모리 누수

    • 메모리 사용이 끝났음에도 불구하고 해제하지 않은 것
  • 이중 해제

    • 이미 해제가 된 메모리임에도 불구하고 또 해제하는것
      • 이미 해제가 된 메모리를 가리키는 포인터를 댕글링 포인터(Dangling Pointer)라 한다.
  • 섣부른 해제

    • 아직 사용이 끝나지 않았음에도 불구하고 해제하는 것

동작 원리

가비지 컬렉션은 가비지 컬렉터가 더이상 사용하지 않는 메모리를 재사용함으로써 동작된다.

  • 추적 가비지 컬렉션

    • 추적 방식에서는 도달 가능성으로 생존을 가정하는데 루트(Root)를 사용하여 해당 메모리까지 도달할 수 있는지 보고, 도달하지 못한 메모리는 가비지로 가정한다.

      검은색 : 도달할 수 있는것들
      흰색 : 도달할 수 없는것들
  • 참조 카운팅

    • 해당 메모리에 참조하는 것이 없을 때 가비지로 가정한다. 참조 카운팅 방법은 순환참조를 주의해야 하는데, 이를 방지하기 위해 약한 참조 라는 개념을 사용한다.

      (순환 참조)

C# 은 가비지 컬렉션을 지원한다.
가비지 컬렉션을 지원하는 언어를 매니지드 언어 라고 한다.
C# 에서 사용하는 방식은 세대별 가지비 수집(Generational Garbage Collection) 이다.

세대

가비지 컬렉터가 관리하는 메모리를 매니지드 힙 이라고 한다.
이 힙을 0세대, 1세대, 2세대 로 나뉘는데 이유는 세대를 나누면 메모리를 재사용하기 용이하기 때문이다.

  • 가비지 컬렉션이 일어날 때 파편화를 방지하기 위해 메모리를 압축하는 데, 힙 전체를 대상으로 하기보다 일부분에서만 수행 하는 게 더 빠르다.
  • 최근에 만들어진 객체일 수록 수명이 짧고 오래 사용된 객체일 수록 수명이 길어 재사용할 메모리를 빠르게 분류할 수 있다.
  • 메모리 할당은 0세대에서만 일어나는데 최근에 만들어진 객체끼리 서로 연관되는 경향이 있어 캐싱 측면에서 좋다.

메모리 할당

C# 에서 모든 참조 타입의 객체는 매니지드 힙의 0세대에 할당된다.
매니지드 힙은 메모리를 미리 시스템으로부터 할당 받아 놓기 때문에(오브젝트 풀링같은 것) 스텍에서 메모리를 할당하는 속도만큼 빠르게 할당할 수 있고, 접근도 빠르게 할 수 있다.
단, 85KB 이상의 크기를 가지는 큰 객체는 LOH라는 2세대 메모리에 할당된다.

메모리 해제

세대별 가비지 컬렉션은 추적 방식을 사용한다.

매니지드 힙을 검사해 가비지를 찾으면 접근할 수 있는 객체를 압축해 가비지의 공간을 덮어버린다. 이 과정에서 참조 변수의 주소값을 모두 수정하며, 각 세대의 시작을 가리키는 포인터 또한 수정한다.

  • 가비지 컬렉션이 일어나는 순서도 정해져 있다.
    가장 먼저 가비지 컬렉션이 일어나는 세대는 0세대다.
    이 과정에서 가비지가 아닌 메모리는 윗 세대로 승격시킨다.
    만약 0세대에서 가비지 컬렉션을 수행했음에도 불구하고 새로운 객체를 만들기 위한 메모리 공간이 충분하지 않다면, 먼저 1, 2세대 순으로 수집을 수행한다. 그럼에도 또 부족하다면 세대 2, 1, 0의 순서로 수집을 수행한다. 이 경우에도 2세대를 제외하곤 세대 승격은 일어난다

주의 사항

우리가 가비지 컬렉션에 대해서 명확히 이해해야 하는 이유는 성능과 직결되기 때문이다.
가비지 컬렉션은 결코 자원을 적게 소모하는 연산이 아니며, 멀티스레드 환경인 경우 가비지 컬렉션이 수행되는 동안 다른 스레드가 중된단다.

  • 빈번한 할당을 조심하자
    • 가비지 컬렉션이 일어나는 조건 중 하나는 객체를 할당할 충분한 공간이 없을 때다, 즉 0세대가 가득 찼을 때를 의미한다. 객체를 빈번하게 생성하면 0세대에 여유 공간이 부족해 가비지 컬렉션이 일어날 수 있다.
  • 너무 큰 객체 할당은 피하도록 하자
    • 앞서 말했듯 85KB 이상의 크기를 가지는 객체는 LOH에 할당되며, LOH에서는 메모리 압축이 일어나지 ㅇ낳아 내부 단편화가 발생할 수 있다.
  • 복잡한 참조 관계를 피하자
    • 가독성도 문제지만 가비지 컬렉션 후에 메모리 주소 관리를 어렵게 한다. 특히나 오래된 세대에서 새로운 세대에 대한 메모리를 참조하게 될 때 수집을 방지하기 위해 쓰기 장벽(Write Barrier)을 만드는 데 이는 많은 성능을 필요로 한다.
  • 관리되지 않는 리소스다 있다
    • 대부분의 리소스는 관리되지만 파일 핸들, 윈도우 핸들, 네트워크 연결 등의 운영체제 리소스를 래핑하는 경우 제대로 정리가 되지 않는다. 이런 경우에는 IDisposable와 using 문을 이용할 수 있다

Unity C#

스크립트 벡엔드

  • Unity에서 사용하는 .NET은 2가지가 있다.
    • Mono
      • 크로스 플랫폼을 지원하는 오픈 소스 .NET
    • IL2CPP
      • Unity가 만든 .NET

가비지 컬렉션

  • .NET 환경이 다르기에 가비지 컬렉션의 방식도 달라진다
  • Unity에서는 Boehm GC를 사용하며, 세대별로 관리되지 않고 메모리의 압축 또한 이러나지 않는다.
    그래서 Unity에서는 메모리 단편화가 일어나 힙이 쉽게 확장되어, 최적화 이슈가 발생할 수 있다.
  • 가비지 컬렉션에 너무 많은 성능이 소모되는 것을 막기 위해 수행 제한 시간을 두어 조금씩 조금씩 가비지 컬렉션을 하는 증분 방식 또한 사용한다.

ㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁㅁ

교수님 정리

.NET

  • CLR과 클래스 라이브러리 세트를 의미함
  • CLR은 C# 코드를 컴파일 한 결과물인 IL 코드를 다시 해당 플랫폼에 맞는 코드로 변환하여 하나의 소스코드로 여러 플랫폼을 지원함
  • .NET에는 공용 타입 시스템이 있다
    • 모든 타입은 값 타입과 참조 타입으로 분류
    • 모든 타입은 System.Object 타입을 상속 받음
    • 이를 명확히 인지하고, 박싱과 언박싱을 피하도록 코드를 작성해야 함

가비지 컬렉션

  • 메모리를 수동으로 관리하는 것은 여러 문제점이 있음
    • 메모리 누수 / 이중 해제 / 섣부른 해제
  • 메모리를 자동으로 관리하는 기술
  • 가비지로 가정하는 방법
    • 추적 : 도달 가능성으로 가비지 판단
    • 참조 카운팅 : 참조 횟수로 가비지 판단
  • 표준 .NET은 세대별 가비지 컬렉션을 사용
    • 메모리 할당은 0세대에서만 일어남
    • 메모리 해제 시, 가비지가 아닌 메모리는 윗세대로 승격됨
  • 가비지 컬렉션이 수행되는 동안에는 프로그램이 멈추기 때문에 이에 유의하여 코드를 작성해야 함.

.NET: Unity

  • Unity의 스크립트 백엔드(스크립트 환경)는 Mono, IL2CPP 두 개가 있음 => 여기서 모든 차이가 발생
  • 세대별로 관리하지 않으며, 메모리 압축 또한 없기 때문에 메모리 단편화가 쉽게 일어나고, 힙도 쉽게 확장된다. 그래서 최적화 이슈가 발생할 수 있다.
profile
해보자요

0개의 댓글