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
日本語訳
한국어
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
虚幻脚本结构体应用
概述
实例
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); } }