Some Simple Optimizations in Unreal Engine
When it boils down to it you should profile things before optimizing however, these things can be added without the need to profile. Also, if I'm wrong about any of these or you have any additional tips to add then please reach out via email russ@trdwll.com.
- TArray.Reserve(size)
- This will reserve memory for the array in advance. Do this before modifying an array to prevent allocations in a loop etc.
When adding elements to a container in a loop, performances can be impacted because of multiple heap allocations to resize the container. - Also, do this on temporary allocations (see the second example). (if you allocate an array in a method and then return that array later or need to do something specific to the results)
// Adds N copies of a character to the end of an array. void AppendCharacterCopies(char CharToCopy, int32 N, TArray<char>& Result) { if (N > 0) { Result.Reserve(Result.Num() + N); for (int32 Index=0; Index < N; Index++) { Result.Add(CharToCopy); } } }
void AppendCharacterCopies(char CharToCopy, int32 N) { if (N > 0) { TArray<char> Result; Result.Reserve(N); for (int32 Index = 0; Index < N; Index++) { Result.Emplace(CharToCopy); }
// do something with Result }
}
- This will reserve memory for the array in advance. Do this before modifying an array to prevent allocations in a loop etc.
- TArray.Emplace(type)
- By using Emplace over Add you don't create a copy of the type. Really only use emplace on types larger than 8 bytes or rather don't use it on things like ints.
Add
(orPush
) will copy (or move) an instance of the element type into the array.Emplace
will use the arguments you give it to construct a new instance of the element type.
- Range-based for loops
- By using range-based for loops you can drop an allocation from the
int32 i = 0; ...
and it also makes your code a bit cleaner.for (const auto& Elem : MyContainer) { // TArray - Elem // TMap - Elem.Key/Elem.Value }
- By using range-based for loops you can drop an allocation from the
- Pass by reference
- Passing by reference you don't copy said member which doesn't do another allocation of the type.
- Don't pass normal types (int/float/bool) by reference as that actually can slow down performance.
- Instead of copying an array or any other type
TArray<uint8> GetMyArray() const { return MyArray; }
return it as a const reference likeconst TArray<uint8>& GetMyArray() const { return MyArray; }
(You can expose this to BP btw)
- Smaller types
- Use smaller types like uint8 or uint16 when possible. Sure, uint16 isn't exposed to Blueprint, so that really limits you however, you may not need to expose that to BP.
- I only pass by value if the type is < 8 bytes.
Type Kind Min/Max Values Size Exposed to BP int8/uint8 8 bit signed/unsigned integer +/- 128 - 0 to 255 1 byte false/true int16/uint16 16 bit signed/unsigned integer +/- 32,768 - 0 to 65,535 2 bytes false/false int32 32 bit signed integer +/- 2,147,483,647 4 bytes true uint32 32 bit unsigned integer 0 to 4,294,967,295 4 bytes false int64 64 bit signed integer +/- 9,223,372,036,854,775,807 8 bytes true uint64 64 bit unsigned integer 0 to 18,446,744,073,709,551,615 8 bytes false FName sequence of chars ∞ 12 bytes true FString sequence of chars ∞ 16 bytes true FText sequence of chars ∞ 24 bytes true
- Tick
- Disable tick on actors that don't need ticking.
- Add
PrimaryActorTick.bCanEverTick = false;
to the constructor of the Actor class. - Tick is much more performance in C++ than in BP, but you still shouldn't tick actors that don't need ticking.
- UMG
- Avoid using the bindings as they're essentially Tick with a pretty name. Instead, use delegates to update and maintain your UI.
- Avoid using the bindings as they're essentially Tick with a pretty name. Instead, use delegates to update and maintain your UI.
- FVector_NetQuantize
- Use these when you don't need a very precise Vector and if it's needing to be sent over the net - otherwise just use FVector.
Type Kind Min/Max Values Size / Net Size Exposed to BP FVector_NetQuantizeNormal Vector (x,y,z) +/- 1 12 bytes / 2 bytes true FVector_NetQuantize Vector (x,y,z) - 0 decimal place of precision. +/- 1,048,576 12 bytes / 2.5 bytes true FVector_NetQuantize10 Vector (x,y,z) - 1 decimal place of precision. +/- 1,677,721.6 12 bytes / 3 bytes true FVector_NetQuantize100 Vector (x,y,z) - 2 decimal place of precision. +/- 10,737,418.24 12 bytes / 3.75 bytes true FVector Vector (x,y,z) - 6 decimal place of precision +/- 3.4E+38 12 bytes / 12 bytes true
- Use these when you don't need a very precise Vector and if it's needing to be sent over the net - otherwise just use FVector.
- Structs
- Instead of passing a ton of variables sometimes, it's better to package them into a struct to send over the net.
- DOREPLIFETIME_CONDITION
- Set members replication condition as necessary.
- Sometimes you don't have to replicate something, so always check if you actually have to.
- Overlap Events
- Disable these if you don't need them on an actor.
- SetGenerateOverlapEvents
- When performing an RPC call in a method do checks before doing an RPC call to have an early out if certain conditions aren't met
... { if (x) { return; }
if (!HasAuthority()) { ServerRPC(...); return; }
}
instead of
… { if (!HasAuthority()) { ServerRPC(…); return; }
if (x) { return; }
}
- ???
until next time