UDN
Search public documentation:

UnrealScriptStructsCH
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 主页 > 虚幻脚本 > 虚幻脚本结构体应用

虚幻脚本结构体应用


概述


虚幻脚本结构体是一种把相关数据成员组织到一起的强大机制。与 C++ 一样,从本质来讲声明或打开结构体的数据成员没有任何使性能变慢的因素。然而,与在 C++中通过值来传递结构体会影响性能一样,这个问题也存在于通过值来传递脚本结构体中。本文档的目的是查看脚本解释器是如何处理值传递的。

请参照虚幻脚本参考指南页面了解有关虚幻脚本的结构体及其它功能的更多信息。

实例


我们以操作几个脚本结构体的简单类为例(请参照下面的 MySlowness.uc)。它可以使用非常常用的形式: 它声明了几个自定义结构体来把类型汇集到更加复杂的实体中;它会将这些结构体的实例声明为 MySlowness 的成员变量;最后,它声明了几个函数来操作这些结构体。

MySlowness 类在每次更新 (tick) 时执行一些工作。首先,它在一些值上进行迭代来查找 MySlowStruct 的最大类型。第二,它会将传入的实例化的 MyMemoryAbusingStructs 改为其它函数的状态。注意,实例是通过存取器函数返回的。MySlowness 中列出的所有函数都是通过值来传递它们的结构体的。这看上去何以这种方式传递其它更加复杂的数据类型一样,比如 Actor、对象等。但是,引擎隐含地把所有从 Object 继承而来的实体都当做引用传递对待而不是值传递。脚本结构体没有这种特殊处理,它通过值传递,从而导致一些性能下降。

首先,我们检查一下使用 MySlowStruct 实例对性能造成的影响。有一种便捷的方法来初始化它的所有值。还有一种方法,假如有两个结构体的实例,该方法可以使用两个结构体实例的最大值创建一个新的结构体。代码会无意识地创建这个结构体的多个副本,从而降低了游戏性能。MySlowStruct 中的流程说明了从 InitMySlowStruct() 函数中返回的值执行了一个到目的存储空间的内存复制。每调用一次这个函数便会发生一次这个过程,得到的结果会被传递到 MaxOfMySlowStruct() 方法中。因为该函数在两个参数上都是通过值传递的,所以它创建了该数据的两个以上副本。最终,得到的数据会被复制到目标变量中。所以,在一个代码块中,我们已经尝试复制了大约 5 次数据。

  MySlowStruct Example
  
  DoSlowness()
     For 1 to 32
        (1) = InitMySlowStruct(passed in values copied to function)   <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
        (2) = InitMySlowStruct(passed in values copied to function(传入要复制到函数中的值))   <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
        MaxOfMySlowStruct(copies the value (1),copies the value (2))   <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
  
  

在这种简单的数据类型上,它复制了超出需要的很多内存,从而给 CPU 施加了压力,并且潜在地影响数据缓存。在更加复杂的类型上,对性能和内存的影响会更加大。我们以 MyMemoryAbusingStruct 为例,来看一下当涉及到更加复杂的类型时会发生什么。注意,MyMemoryAbusingStruct 的每个成员都是一个数组。我们的数组容器占用 12 个字节的内存,所以结构体的每个实例占用 32 个字节。然而,只要您想在这些数组中添加项,数组必须分配内存来存储那个数据。因为结构体是复杂的数组类型,它不能使用高效的内存复制,而需要手动地复制数组中的每个元素。

首先,代码调用存取器来获取在 Instance1 中存储的数据。所有的返回值都不能通过引用来传递,必须进行复制。这样需要分配一个结构体大小(36 字节)的内存来存储结构体;接下来,每个数组需要分配一个 数据类型大小*元素数量 的内存来存储数组;最后,使用字符串数组来复制每个数组中的每个元素,这样便会产生额外的内存分配及副本,因为字符串是字符数组。然后该结构体会被传入到 SetGroupOfStuff() 中,从而导致发生另一个完全的复制序列。下一个 GetInstance1() 调用也会执行相同的复制过程,因为脚本编译器是一个没有优化的编译器。其它的函数调用也会重复这种形式,从而产生了该数据的更多的副本,所以最终产生 6 个(如果函数 native 的则是 9 个)独立的 Instance1 的副本和 2 个 Instance2(如果函数 native 的则是 2 个)的副本。所有的这些内存分配都会对底层的内存管理器造成压力,冲刷数据缓存、占用大量的 CPU 时间,但是却没有获得任何真正的利益。

  MyMemoryAbusingStruct Example
  
  
  
  DoAbuse()
     (1) = GetInstance1()                  <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
     SetGroupOfStuff(copies the value (1))
     (2) = GetInstance1()                  <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
     (3) = GetInstance2()                  <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
     SetGroupOfStuffEx(copies the value (2),copies the value (3))
     (4) = GetInstance1()                  <--- Return value must make a copy the struct(返回值必须创建一个该结构体的副本)
     SetGroupOfStuff(copies the value (4))
  
  

幸运的是,实现这个代码的优化是非常简单的,我们从中可以产生巨大的收益。在本文档的结尾处,有一个 MySlowness 类的优化版本,它比原来的那个实现快 30 倍。

DoSlowness() 的第二个版本有一些重要的优化。第一,它不再调用 InitMySlowStruct() 方法,因为它的返回值创建了一个我们不需要的副本。它没有影响脚本解释器把所有本地变量都置为 0 的事实,所以它正在把已经是 0 的值设置为 0。注意,在循环中,仅正在改变的值会被分配给结构体中的成员变量。第二个优化是 MaxOfMySlowStruct() 使用引用来传入所有数据。这个方法在决定每个元素的最大值时没有进行内存复制,并且不需要对从两个结构体中构建出的值进行内存复制。这些简单的改变使得代码的运行速度提高 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 进行一些处理
     }
  }
  
  

将同样的优化应用到其它的函数中将会产生更加大的收获。因为删除了保证返回一个副本的存取器函数。SetGroupOfStuff() 和 SetGroupOfStuffEx() 方法变为通过引用传递而不是通过值传递。最终产生了令人难以置信的结果,当在它们中使用具有大量数据的数组时,您会发现它比使用旧的方法快 364 倍。把所有其它的性能消耗包括在内,通过简单的改变,代码的最终版本要比原来快 30 倍(包含 Tick() 时间)。

  /** 表明内存滥用 */
  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);
     }
  }
  
  

注意: 更加重要的事情是请确保您在声明 native 函数时合理地使用脚本"const out"语法,因为它基于每个函数会导致比仅具有脚本的情况下产生多个副本。这个问题的原因是脚本制作一个副本来向 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 的存取器 */
  simulated function MyMemoryAbusingStruct GetInstance1()
  {
     return Instance1;
  }
  
  /** 实例 2 的存取器 */
  simulated function MyMemoryAbusingStruct GetInstance2()
  {
     return Instance2;
  }
  
  /** 实例 3 的存取器 */
  simulated function MyMemoryAbusingStruct GetInstance3()
  {
     return Instance3;
  }
  
  /** 实例 4 的存取器 */
  simulated function MyMemoryAbusingStruct GetInstance4()
  {
     return Instance4;
  }
  
  /**
   * 初始化我的 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);
     }
  }