UDN
Search public documentation:
UnrealScriptStructs
日本語訳
中国翻译
한국어
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
UE3 Home > UnrealScript > Unreal Script Struct Usage
Unreal Script Struct Usage
Overview
Example Case
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 structOn a simple data type such as this, it is putting pressure on the CPU to copy more memory around than needed and potentially affecting the data cache. On more complex types, the performance and memory considerations are much greater. Let's take a look at the MyMemoryAbusingStruct as an example of what happens when more complex types are involved. Notice that each member of the MyMemoryAbusingStruct is an array. Our array container consumes 12 bytes of memory, so the struct uses just 36 bytes per instance. However, once you add items to those arrays, the arrays must allocate memory to hold that data. Because the structs are complex types of arrays, each element of those arrays needs to be copied manually instead of using an efficient memory copy. First, the code calls the accessor function to get the data held in Instance1. All return values cannot be passed by ref and must be copied. This generates a memory allocation to hold the size of the struct (36 bytes); followed by a each array performing an allocation to hold data type size * number of items; and finally, each element in each array is copied with the string arrays generating additional allocations and copies since strings are arrays of characters. That struct is then passed into SetGroupOfStuff() causing another full copy sequence to happen. The next call to GetInstance1() also performs the same copy process because the script compiler is a non-optimizing compiler. This pattern repeats with the other function calls making more and more copies of the data with results being 6 separate (9 if native) copies of Instance1 and 2 (4 if native) copies of Instance2. All of these allocations cause pressure on the underlying memory manager, thrash the data cache, and use up tons of CPU time for no real benefit.
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))Fortunately, the optimization of this code is pretty straight forward to do and generates huge benefits. At the end of this document, there is an optimized version of the MySlowness class that is overall 30 times faster than the naive implementation. The second version of DoSlowness() has some important optimizations. First, it no longer calls the InitMySlowStruct() method as the return value performs an unneeded copy. It also doesn't leverage the fact that the script interpreter zeros all local variables, so it was assigning values to zero that were already zero. Note in the loop only the changing values are assigned to members in the struct. The second optimization is that the MaxOfMySlowStruct() method is passing all data by reference. This means that there is no memory copying going on to determine the max of each element and there is no copying of memory to return the value that is built from the two structs. These simple changes made the code run 2.5 times faster.
/** Let's call our slow code */ 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); // Do something with MaxSlowStruct } }Applying the same optimizations to the other functions yeilds more impressive gains. Because the accessor functions guarantee a copy, they were removed. The SetGroupOfStuff() and SetGroupOfStuffEx() methods were changed to pass by reference instead of value. The final result is a mind boggling 364 times faster than the old way when using arrays with large amounts of data in them and now copies the data zero times. Including all other overhead, the final version of the code is 30 times faster (Tick() time inclusive) with very simple changes.
/** Illustrates memory abuse */ 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); } }NOTE: It is even more important to make sure your native functions are declared properly using scripts "const out" syntax because that incurs one more copy per function parameter than in the script only case. The reason for this is that script makes a copy to pass to the C++ code and the C++ layer also makes a copy because the struct is passed by reference. This may be the only time it's slower to move something to C++ from script.
MySlowness.uc
class MySlowness extends Actor; /** This struct will be needlessly copied many, many times */ struct MySlowStruct { var float A, B, C, D; }; /** This struct will put pressure on the allocator and thrash the cache needlessly */ struct MyMemoryAbusingStruct { var array<MySlowStruct> SlowStructs; var array<string> SomeStrings; var array<vector> SomeVectors; }; // A bunch of instances of memory abuse var MyMemoryAbusingStruct Instance1; var MyMemoryAbusingStruct Instance2; var MyMemoryAbusingStruct Instance3; var MyMemoryAbusingStruct Instance4; var float ElapsedTime; /** Accessor to instance 1 */ simulated function MyMemoryAbusingStruct GetInstance1() { return Instance1; } /** Accessor to instance 2 */ simulated function MyMemoryAbusingStruct GetInstance2() { return Instance2; } /** Accessor to instance 3 */ simulated function MyMemoryAbusingStruct GetInstance3() { return Instance3; } /** Accessor to instance 4 */ simulated function MyMemoryAbusingStruct GetInstance4() { return Instance4; } /** * Initializes my slow struct */ 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; } /** Let's do some needless copies */ 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; } /** Let's call our slow code */ 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)); // Do something with MaxSlowStruct } } /** For showing the memory abuse */ function SetGroupOfStuff(MyMemoryAbusingStruct MemAbuse,float SomeAbuseValue) { } /** For showing the memory abuse */ function SetGroupOfStuffEx(MyMemoryAbusingStruct MemAbuse,MyMemoryAbusingStruct MoreAbuse) { } /** Illustrates memory abuse */ 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); } } /** Do the bad stuff once per frame */ 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; /** This struct will be needlessly copied many, many times */ struct MySlowStruct { var float A, B, C, D; }; /** This struct will put pressure on the allocator and thrash the cache needlessly */ struct MyMemoryAbusingStruct { var array<MySlowStruct> SlowStructs; var array<string> SomeStrings; var array<vector> SomeVectors; }; // A bunch of instances of memory abuse var MyMemoryAbusingStruct Instance1; var MyMemoryAbusingStruct Instance2; var MyMemoryAbusingStruct Instance3; var MyMemoryAbusingStruct Instance4; var float ElapsedTime; /** Let's do some needless copies */ 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); } /** Let's call our slow code */ 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); // Do something with MaxSlowStruct } } /** For showing the memory abuse */ function SetGroupOfStuff(const out MyMemoryAbusingStruct MemAbuse,float SomeAbuseValue) { } /** For showing the memory abuse */ function SetGroupOfStuffEx(const out MyMemoryAbusingStruct MemAbuse,const out MyMemoryAbusingStruct MoreAbuse) { } /** Illustrates memory abuse */ 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); } } /** Do the bad stuff once per frame */ event Tick(float DeltaTime) { DoSlowness(); ElapsedTime += DeltaTime; if (ElapsedTime > 0.5) { ElapsedTime = 0.0; DoAbuse(true); } else { DoAbuse(false); } }