ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 언리얼 컨테이너 라이브러리 I - Array
    게임 개발/Unreal 2024. 1. 31. 22:09

    언리얼 컨테이너 라이브러리

    • 언리얼 엔진이 자체 제작해 제공하는 자료구조 라이브러리
    • 줄여서 UCL(Unreal Container Library)라고도 함.
    • 언리얼 오브젝트를 안정적으로 지원하며 다수 오브젝트 처리에 유용하게 사용됨.
    • 언리얼 C++은 다양한 자료구조 라이브러리를 직접 만들어 제공하고 있음.
    • 실제 게임 제작에 유용하게 사용되는 라이브러리로 세 가지를 추천함.

     

    C++ STL과 언리얼 컨테이너 라이브러리의 차이점

    • C++ STL은 범용적으로 설계되어 있다.
    • C++ STL은 표준이기 때문에 호환성이 높다.
    • C++ STL에는 많은 기능이 엮여 있어 컴파일 시간이 오래걸림
    • 언리얼 컨테이너 라이브러리는 언리얼 엔진에 특화되어 있음
    • 언리얼 컨테이너 라이브러리는 언리얼 오브젝트 구조를 안정적으로 지원한다.
    • 언리얼 컨테이너 라이브러니는 가볍고 게임 제작에 최적화되어 있음.

     

    언리얼 C++ 주요 컨테이너 라이브러리

    • 두 라이브러리의 이름과 용도는 유사하지만, 내부적으로 다르게 구현되어 있음.
    • TArray: 오브젝트를 순서대로 담아 효율적으로 관리하는 용도로 사용
    • TSet: 중복되지 않는 요소로 구성된 집합을 만드는 용도로 사용
    • TMap: 키,밸류 조합의 레코드를 관리하는 용도로 사용

     

    TArray

    • TArray는 가변 배열(Dynamic Array) 자료구조
    • STL의 vector와 동작 원리가 유사함
    • 게임 제작에서는 가변 배열 자료구조를 효과적을 활용하는 것이 좋음
    • 데이터가 순차적으로 모여있기 때문에 메모리를 효과적으로 사용할 수 있고 캐시 효율이 높다.
    • 컴퓨터 사양이 좋아지면서, 캐시 지역성(Locality)으로 인한 성능 향상은 굉장히 중요해짐
    • 임의 데이터의 접근이 빠르고, 고속으로 요소를 순회하는 것이 가능
    • 가변배열의 단점
    • 맨 끝에 데이터를 추가하는 것은 가볍지만, 중간에 요소를 추가하거나 삭제하는 작업은 비용이 큼
    • 데이터가 많이질수록 검색, 삭제, 수정 작업이 느려지기 떄문에, 많은 수의 데이터에서 검색 작업이 빈번하게 일어난다면 TArray대신 TSet을 사용하는 것이 좋음

    TArray 언리얼 공식 문서

     

    TArray는 신속성, 메모리 효율성, 안정성을 염두에 두고 디자인된 컨테이너 클래스.

    TArray유형은 주로 엘리먼트 유형, 부로 얼로케이터로 정의 된다.

     

    엘리먼트 유형은 배열에 저장되는 오브젝트 유형으로 그 엘리먼트 전부 엄격히 같은 유형이어야 한다.

    유형이 다른 엘리먼트를 하나의 TArray에 저장할 수 없다.

    TArray 는 값 유형으로, int32나 float같은 다른 내장형과 비슷하게 취급해야 합니다. 확장을 염두에 두지는 않았기에, TArray 인스턴스를 new및  delete로 생성 또는 소멸시키는 것은 좋치 않다.

     

    배열 만들고 채우기

     

    배열 정의

    TArray<int32> IntArray;

     

    얼로케이터가 지정되지 않았으니 TArray는 기본 힙 기반 얼로케이터를 사용한다.(별도의 커스텀 얼로케이터가 지정되지 않았을 경우, 기본 얼로케이터를 사용한다.)

    TArray는 기본 힙 기반 얼로케이터를 사용한다. 

    이 시점에서는 아직 할당된 메모리가 없다.

     

    TArray 채우기 방법

    Init 함수 사용

    IntArray.Init(10, 5);
    // IntArray == [10,10,10,10,10]

     

    Add 와 Emplace함수 사용

    TArray<FString> StrArr;
    StrArr.Add    (TEXT("Hello"));
    StrArr.Emplace(TEXT("World"));
    // StrArr == ["Hello","World"]

     

    배열의 얼로케이터는 배열에 새 엘리먼트가 추가될 때 필요에 따라 메모리를 제공한다.

    기본 얼로케이터는 다수의 새 엘리먼트가 현재 배열 크기를 넘어설 때마다 충분한 메모리를 추가한다.

     

    Add와 Emplace의 차이

    Add(또는 push)는 엘리먼트 유형의 인스턴스를 배열에 복사(또는 이동) 합니다.

    Emplace는 지정한 인수를 사용하여 엘리먼트 유형의 인스턴스를 새로 생성한다.

     

    Emplace는 임새 생성 후 컨테이너에 복사 내지 이동하는 불필요한 절차를 피할수 있어 효율적이지만 가독성은 Add가 낫다.

     

    연산자

     

    할당 연산자를 통해서 값을 넣을수도 있는데 이때 복사한뒤에 넣는다 (Add 함수)(Emplace 아님)

    TArray<int32> ValArr3;
    ValArr3.Add(1);
    ValArr3.Add(2);
    ValArr3.Add(3);
    
    auto ValArr4 = ValArr3;
    // ValArr4 == [1,2,3];
    ValArr4[0] = 5;
    // ValArr3 == [1,2,3];
    // ValArr4 == [5,2,3];

     

    Append 함수의 대안으로, operator+= 를 통해 배열을 연결시킬 수 있다.

    ValArr4 += ValArr3;
    // ValArr4 == [5,2,3,1,2,3]

     

     

    TArray 에는 이진 힙 데이터 구조체를 지원하는 함수가 있다.

    Heapify 함수를 사용하여 기존 배열을 힙으로 변환시킬 수 있다.

    TArray<int32> HeapArr;
    for (int32 Val = 10; Val != 0; --Val)
        HeapArr.Add(Val);
    // HeapArr == [10,9,8,7,6,5,4,3,2,1]
    HeapArr.Heapify();
    // HeapArr == [1,2,4,3,6,5,8,10,7,9]

     

    슬랙

     

    배열은 크기변경이 가능하므로, 메모리 사용량이 가변적이다. 

    배열이 추가될 때마다 매번 재할당을 피하기 위해, 얼로케이터는 보통 용청한 것보다 넉넉한 메모리를 제공하여 앞으로의 Add 호출시 재할당에 드는 퍼포먼스 비용을 물지 않도록 한다. 마찬가지로 엘리멘트를 삭제한다고 메모리가 해제되지는 않으며, 배열에 slack(여유분, 슬랙), 즉 현재 사용되지는 않아도 사실상 미리 할당된 엘리먼트 저장 슬롯을 남길 뿐이다. 배열의 슬랙 양은 현재 컨테이너에 있는 엘리먼트의 수와 엘리먼트를 몇 개나 더 추가하면 다음 할당이 일어나는지에 대한 차이로 정의할 수 있다.

     

    기본 생성된 배열은 메모리 할당이 없으므로, 초기 슬랙은 0이다.

     

    GetSlack 함수를 사용하면 배열의 슬랙 크기가 얼마나 되는지 알아낼 수 있다.

    다른방법으로는, Max함수를 사용하면 재할당이 일어나기 전까지 배열에 저장할 수 있는 엘리먼트 최대 개수를 구할 수 있다. GetSlack은 Max와 Num의 차이가 같습니다.

    TArray<int32> SlackArray;
    // SlackArray.GetSlack() == 0
    // SlackArray.Num()      == 0
    // SlackArray.Max()      == 0
    
    SlackArray.Add(1);
    // SlackArray.GetSlack() == 3
    // SlackArray.Num()      == 1
    // SlackArray.Max()      == 4
    
    SlackArray.Add(2);
    SlackArray.Add(3);
    SlackArray.Add(4);
    SlackArray.Add(5);
    // SlackArray.GetSlack() == 17
    // SlackArray.Num()      == 5
    // SlackArray.Max()      == 22

    재할당 이후 컨테이너의 슬랙 양은 얼로케이터에 의해 결정되므로, 사용자가 남아있는 슬랙이 일정할 거라 믿어서는 안된다.

     

    슬랙 관리가 필수는 아니지만, 알아 두면 배열 최적화 힌트를 얻는데 도움이 될 수 있다. 

    예를 들어 배열에 엘리먼트를 100개쯤 추가해야 겠다고 알고 있는 상황에서, 슬랙이 최소 100이상은 되는 것으로 알고 있다면 그냥 추가해도 할당이 새로 일어나지 않을 것이다.

     

    원시 메모리

     

    TArray 는 궁극적으로 할당된 메모리를 둘러싼 포장 단위일 뿐이다. 그렇기에 할당된 바이트를 직접 변경하거나 엘리먼트를 직접 만드는 것과 같이 처리해 주면 유용할 때가 있다. 물론 TArray 는 항상 그 안의 정보를 가지고 할 수 있는 최선을 다하도록 노력하지만, 가끔은 조금 더 낮은 레벨로 내려갈 필요도 있다.

     

    #이득우의 언리얼 프로그래밍1

    #https://docs.unrealengine.com/4.26/ko/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/TArrays/

    댓글

Designed by Tistory.