UDN
Search public documentation:

UnrealScriptStructsKR
English Translation
日本語訳
中国翻译

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 홈 > 언리얼스크립트 > 언리얼스크립트 구조체 사용법

언리얼스크립트 구조체 사용법


문서 변경내역: Joe Graf 작성.

개요


UnrealScript 구조체는 관련 데이터 멤버를 서로 함께 그룹 연결하는 것에 대한 강력한 메케니즘입니다. C++에서와 같이 구조체의 데이터 멤버를 선언하거나 또는 여는 것은 본질적으로 느리지 않습니다. 그러나 C++에서 구조체를 값에 의해 전달하는 것이 성능에 영향을 미치는 것처럼 스크립트 구조체를 값에 의해 전달하는 것은 동일한 성능적 영향을 가져옵니다. 본 문서의 목적은 값에 의한 전달이 스크립트 해석기에 의해 처리되는지를 살펴보는 것입니다.

구조체 및 다른 UnrealScript의 특징에 대한 좀 더 자세한 내용은 UnrealScript 참조 를 참조해 주십시오.

예제 경우


여러 스크립트 구조체를 조작하는 간단한 클래스를 살펴보겠습니다(아래의 MySlowness.uc를 참조해 주십시오). 이것은 매우 일반적인 패턴을 사용합니다. 유형을 좀 더 복잡한 실체의 집합체를 만드는 것에 대한 몇 가지 사용자 지정 구조체를 선언합니다. 그러한 구조체의 인스턴스를 MySlowness 클래스의 멤버로 선언합니다. 최종적으로 그러한 구조체에서 작동할 몇 가지 함수를 선언합니다.

MySlowness 클래스는 각 틱에서 어떤 작업을 실행합니다. 첫째 MySlowStruct의 최대 유형을 찾기 위해 값의 범위에 걸쳐 반복합니다. 둘째 인스턴스화된 MyMemoryAbusingStructs의 어떤 버전이 다른 함수에 상태로 전달되는지를 변경합니다. 해당 인스턴스는 accessor 함수를 통해 반환됩니다. MySlowness 에 열거된 모든 함수는 자신의 구조체를 값으로 전달합니다. 액터, 객체 등과 같은 훨씬 더 많은 수의 복잡한 데이터 유형이 이런 방식으로 전달되기 때문에 이것은 자연스러워 보입니다. 그러나 엔진은 명시적으로 모든 Object에서 파생된 실체들을 값에 의한 전달이 아닌 참조에 의한 전달로 취급합니다. 값에 의한 절달에 성능 영향이 있기 때문에 스크립트 구조체는 이 특별 처리를 갖지 못합니다.

먼저 MySlowStruct 예를 사용하여 성능 영향을 살펴보겠습니다. 이것의 모든 값을 초기화시키는 편리한 메서드가 있습니다. 구조체가 해당 구조체의 주어진 2개의 인스턴스 중 그 최대 값을 사용하여 새로운 구조체를 만드는 또 다른 메서드가 있습니다. 구조체의 의도되지 않은 수의 복사본을 만드는 코드가 게임의 속도를 느리게 만듭니다. MySlowStruct에서의 흐름은 InitMySlowStruct()로부터의 반환 값이 대상으로의 메모리 복사를 실행하는 것을 보여줍니다. 이것은 각 호출당 한 번 결과를 MaxOfMySlowStruct() 메서드에 전달하면서 발생합니다. 함수는 두 매개 변수에 값으로 전달되기 때문에 해당 데이터의 복사본을 2개 더 만듭니다. 마지막으로, 최종 결과는 대상 변수로 복사되어집니다. 따라서 한 코드 뭉치에서 데이터를 약 5번 복사하는 것을 처리하였습니다.

MySlowStruct 예제

DoSlowness()
   For 1 to 32
      (1) = InitMySlowStruct(함수에 복사되어진 전달된 값)   <--- 반환 값은 반드시 구조체를 복사해야 함.
      (2) = InitMySlowStruct(함수에 복사되어진 전달된 값)   <--- 반환 값은 반드시 구조체를 복사해야 함.
      MaxOfMySlowStruct(값(1)을 복사함,값(2)를 복사함)   <--- 반환 값은 반드시 구조체를 복사해야 함.

이것과 같은 간단한 데이터 유형에서 필요 이상의 메모리를 복사하기 위해 CPU에 무리를 가하게 되고, 잠재적으로 데이터 캐시에 영향을 끼치게 됩니다. 좀 더 복잡한 유형에서는 성능 및 메모리 고려가 훨씬 더 커집니다. 좀 더 복잡한 유형이 연관될 시 무슨 일이 발생하는지의 예로써 MyMemoryAbusingStruct를 살펴보겠습니다. MyMemoryAbusingStruct의 각 멤버가 배열인 것에 주의하십시오. 당사의 배열 컨테이너는 12 바이트의 메모리를 소비합니다. 따라서 구조체는 인스턴스당 36 바이트만을 사용합니다. 그러나 일단 항목을 그러한 배열에 추가하면 배열은 그 데이터를 유지하기 위해 반드시 메모리를 할당해야 합니다. 구조체는 복잡한 유형의 배열이기 때문에 그러한 배열의 각 요소는 효과적인 메모리 복사를 사용하는 것 대신에 수동으로 복사되어질 필요가 있습니다.

먼저 코드는 Instance1에 데이터가 보관되도록 하기 위해 accessor 함수를 호출합니다. 모든 반환 값은 참조로 전달될 수 없고 반드시 복사되야 합니다. 이것은 구조체(36 바이트)의 크기를 보관하기 위해 메모리 할당을 생성합니다. 그런 다음, 각 배열은 데이터 유형 크기 * 항목의 수를 보관하기 위해 할당을 실행합니다. 마지막으로 각 배열에 있는 각 요소는 문자열이 문자의 배열이기 때문에 추가 할당 및 복사를 생성하는 문자열 배열을 사용하여 복사되어집니다. 이 구조체는 SetGroupOfStuff()으로 전달되어 또 다른 전체 복사 시퀀스가 발생하도록 유발합니다. 다음, GetInstance1()으로의 호출은 스크립트 컴파일러가 최적화하는 컴파일러가 아니기 때문에 동일한 복사 과정을 실행합니다. 이 패턴은 다른 함수 호출로 반복되어져 해당 데이터를 복사본을 더 많이 만들게 되어 6개(native인 경우 9개) 의 Instance1의 별도복사본과 2개(native인 경우 4개)의 Instance2의 복사본 만드는 결과를 초래하게 됩니다. 이러한 모든 할당은 기본 메모리 관리자에 부담을 초래하여 데이터 캐시를 무리하게 사용하고 실제 혜택 없이 엄청난 CPU 시간을 사용하게 됩니다.

MyMemoryAbusingStruct 예제



DoAbuse()
   (1) = GetInstance1()                  <--- 반환 값은 반드시 구조체를 복사해야 함.
   SetGroupOfStuff(값(1)을 복사함)
   (2) = GetInstance1()                  <--- 반환 값은 반드시 구조체를 복사해야 함.
   (3) = GetInstance2()                  <--- 반환 값은 반드시 구조체를 복사해야 함.
   SetGroupOfStuffEx(값(2)를 복사함,값(3)를 복사함)
   (4) = GetInstance1()                  <--- 반환 값은 반드시 구조체를 복사해야 함.
   SetGroupOfStuff(값(4)을 복사함)

다행히도 이 코드의 최적화는 하기 매우 간단하고 큰 혜택을 생성합니다. 본 문서의 끝에 순진한 구현 보다 전체 30배 더 빠른 MySlowness 클래스의 최적화된 버전이 있습니다.

DoSlowness()의 두 번째 버전은 몇 가지 중요한 최적화를 가지고 있습니다. 첫째, 반환 값이 필요하지 않은 복사를 실행하기 때문에 InitMySlowStruct() 메서드를 더 이상 호출하지 않습니다. 스크립트 해석기가 모든 로컬 변수를 0으로 만든다는 사실을 사용하지 않습니다. 따라서 이미 0이 된 값들에 0을 지정하였습니다. 루프에서 변경 값만이 구조체의 멤버에 지정됩니다. 두 번째 최적화는 MaxOfMySlowStruct() 메서드가 모든 데이터를 참조로 전달하는 것입니다. 이는 즉, 각 요소의 최대를 결정하기 위해 메모리 복사가 진행되지 않고, 2개의 구조체로부터 빌드된 값을 반환하기 위한 메모리의 복사도 없습니다. 이런 간단한 변경 사항은 코드가 2.5배 더 빠르게 실행되도록 합니다.

/** slow 코드를 호출합시다. */
function DoSlowness()
{
   local int Counter;
   local MySlowStruct First,Second,MaxSlowStruct;
   First.A = 1.0;
   Second.C = 1.0;

   for (Counter = 0; Counter < 32; Counter++)
   {
      First.D = float(Counter);
      Second.A = float(Counter);
      MaxOfMySlowStruct(MaxSlowStruct,First,Second);
      // MaxSlowStruct를 가지고 무언가를 하십시오.
   }
}

동일한 최적화를 다른 함수에 적용하면 좀 더 인상적인 향상을 얻게 됩니다. accessor 함수는 복사가 확실히 발생하게 하기 때문에 제거되었습니다. SetGroupOfStuff()과 SetGroupOfStuffEx() 메서드는 값 대신에 참조로 전달되도록 변경되었습니다. 대량의 데이터가 포함된 배열을 사용할 경우 최종 결과는 놀랍게도 이전 방법 보다 364배 더 빨라집니다. 이제 데이터를 0번 복사합니다. 다른 모든 오버헤드를 포함한 코드의 최종 버전은 아주 간단한 변경 사항으로 30배 더 빨라집니다(Tick() 시작 inclusive).

/** 메모리 남용을 설명함 */
function DoAbuse(bool bShouldUse2ndSet)
{
   if (bShouldUse2ndSet)
   {
      SetGroupOfStuff(Instance1,1.0);
      SetGroupOfStuffEx(Instance1,Instance2);
      SetGroupOfStuff(Instance1,0.0);
   }
   else
   {
      SetGroupOfStuff(Instance3,1.0);
      SetGroupOfStuffEx(Instance3,Instance4);
      SetGroupOfStuff(Instance3,0.0);
   }
}

주의: "const out" 구문은 스크립트만을 사용한 경우 보다 함수 매개 변수당 1번 이상의 복사를 초래하기 때문에 "const out" 구문 스크립트를 올바르게 사용하여 native 함수를 선언하도록 하는 것이 더욱 더 중요합니다. 이것의 이유는 스크립트가 C++ 코드에 전달할 복사본을 만들고 구조체는 참조로 전달되기 때문에 C++ 레이어 또한 복사본을 만들기 때문입니다. 이것은 무엇인가를 스크립트에서 C++ 이동시키는 것이 더 느리게 되는 유일한 시간일 수 있습니다.

MySlowness.uc


class MySlowness extends Actor;

/** 이 구조체는 쓸데없이 너무 여러 번 복사됩니다.*/
struct MySlowStruct
{
   var float A, B, C, D;
};

/** 이 구조체는 할당자에 부담을 가하고 쓸데없이 캐시를 남용합니다. */
struct MyMemoryAbusingStruct
{
   var array<MySlowStruct> SlowStructs;
   var array<string> SomeStrings;
   var array<vector> SomeVectors;
};

// 메모리 남용의 여러 인스턴스
var MyMemoryAbusingStruct Instance1;
var MyMemoryAbusingStruct Instance2;
var MyMemoryAbusingStruct Instance3;
var MyMemoryAbusingStruct Instance4;

var float ElapsedTime;

/** 인스턴스 1으로의 Accessor */
simulated function MyMemoryAbusingStruct GetInstance1()
{
   return Instance1;
}

/** 인스턴스 2로의 Accessor */
simulated function MyMemoryAbusingStruct GetInstance2()
{
   return Instance2;
}

/** 인스턴스 3로의 Accessor */
simulated function MyMemoryAbusingStruct GetInstance3()
{
   return Instance3;
}

/** 인스턴스 4로의 Accessor */
simulated function MyMemoryAbusingStruct GetInstance4()
{
   return Instance4;
}

/**
 * my slow 구조체를 초기화합니다.
 */
function MySlowStruct InitMySlowStruct(float A,float B,float C,float D)
{
   local MySlowStruct MSS;
   MSS.A = A;
   MSS.B = B;
   MSS.C = C;
   MSS.D = D;
   return MSS;
}

/** 쓸데없는 복사를 합시다. */
function MySlowStruct MaxOfMySlowStruct(MySlowStruct SlowA,MySlowStruct SlowB)
{
   local MySlowStruct MSS;
   MSS.A = Max(SlowA.A,SlowB.A);
   MSS.B = Max(SlowA.B,SlowB.B);
   MSS.C = Max(SlowA.C,SlowB.C);
   MSS.D = Max(SlowA.D,SlowB.D);
   return MSS;
}

/** slow 코드를 호출합시다. */
function DoSlowness()
{
   local int Counter;
   local MySlowStruct MaxSlowStruct;

   for (Counter = 0; Counter < 32; Counter++)
   {
      MaxSlowStruct = MaxOfMySlowStruct(InitMySlowStruct(1.0,0.0,0.0,float(Counter)),InitMySlowStruct(float(Counter),0.0,1.0,0.0));
      // MaxSlowStruct를 가지고 무언가를 하십시오.
   }
}

/** 메모리 남용을 보여주기 위함 */
function SetGroupOfStuff(MyMemoryAbusingStruct MemAbuse,float SomeAbuseValue)
{
}

/** 메모리 남용을 보여주기 위함 */
function SetGroupOfStuffEx(MyMemoryAbusingStruct MemAbuse,MyMemoryAbusingStruct MoreAbuse)
{
}

/** 메모리 남용을 설명함 */
function DoAbuse(bool bShouldUse2ndSet)
{
   if (bShouldUse2ndSet)
   {
      SetGroupOfStuff(GetInstance1(),1.0);
      SetGroupOfStuffEx(GetInstance1(),GetInstance2());
      SetGroupOfStuff(GetInstance1(),0.0);
   }
   else
   {
      SetGroupOfStuff(GetInstance3(),1.0);
      SetGroupOfStuffEx(GetInstance3(),GetInstance4());
      SetGroupOfStuff(GetInstance3(),0.0);
   }
}

/** 프레임당 한 번씩 나쁜 일을 합니다. */
event Tick(float DeltaTime)
{
   DoSlowness();

   ElapsedTime += DeltaTime;
   if (ElapsedTime > 0.5)
   {
      ElapsedTime = 0.0;
      DoAbuse(true);
   }
   else
   {
      DoAbuse(false);
   }
}

MySlownessOptimized.uc


class MySlownessOptimized extends Actor;

/** 이 구조체는 쓸데없이 너무 여러 번 복사됩니다.*/
struct MySlowStruct
{
   var float A, B, C, D;
};

/** 이 구조체는 할당자에 부담을 가하고 쓸데없이 캐시를 남용합니다. */
struct MyMemoryAbusingStruct
{
   var array<MySlowStruct> SlowStructs;
   var array<string> SomeStrings;
   var array<vector> SomeVectors;
};

// 메모리 남용의 여러 인스턴스
var MyMemoryAbusingStruct Instance1;
var MyMemoryAbusingStruct Instance2;
var MyMemoryAbusingStruct Instance3;
var MyMemoryAbusingStruct Instance4;

var float ElapsedTime;

/** 쓸데없는 복사를 합시다. */
function MaxOfMySlowStruct(out MySlowStruct MaxStruct,const out MySlowStruct SlowA,const out MySlowStruct SlowB)
{
   MaxStruct.A = Max(SlowA.A,SlowB.A);
   MaxStruct.B = Max(SlowA.B,SlowB.B);
   MaxStruct.C = Max(SlowA.C,SlowB.C);
   MaxStruct.D = Max(SlowA.D,SlowB.D);
}

/** slow 코드를 호출합시다. */
function DoSlowness()
{
   local int Counter;
   local MySlowStruct First,Second,MaxSlowStruct;
   First.A = 1.0;
   Second.C = 1.0;

   for (Counter = 0; Counter < 32; Counter++)
   {
      First.D = float(Counter);
      Second.A = float(Counter);
      MaxOfMySlowStruct(MaxSlowStruct,First,Second);
      // MaxSlowStruct를 가지고 무언가를 하십시오.
   }
}

/** 메모리 남용을 보여주기 위함 */
function SetGroupOfStuff(const out MyMemoryAbusingStruct MemAbuse,float SomeAbuseValue)
{
}

/** 메모리 남용을 보여주기 위함 */
function SetGroupOfStuffEx(const out MyMemoryAbusingStruct MemAbuse,const out MyMemoryAbusingStruct MoreAbuse)
{
}

/** 메모리 남용을 설명함 */
function DoAbuse(bool bShouldUse2ndSet)
{
   if (bShouldUse2ndSet)
   {
      SetGroupOfStuff(Instance1,1.0);
      SetGroupOfStuffEx(Instance1,Instance2);
      SetGroupOfStuff(Instance1,0.0);
   }
   else
   {
      SetGroupOfStuff(Instance3,1.0);
      SetGroupOfStuffEx(Instance3,Instance4);
      SetGroupOfStuff(Instance3,0.0);
   }
}

/** 프레임당 한 번씩 나쁜 일을 합니다. */
event Tick(float DeltaTime)
{
   DoSlowness();

   ElapsedTime += DeltaTime;
   if (ElapsedTime > 0.5)
   {
      ElapsedTime = 0.0;
      DoAbuse(true);
   }
   else
   {
      DoAbuse(false);
   }
}