Some Simple Optimizations in Unreal Engine

I talk about some simple optimizations you can do in your Unreal Engine project.

3 min read
148 views

When it boils down to it you should profile things before optimizing however, these things can be added without the need to profile.

 

  • 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. (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.Add(CharToCopy);
              }
      
              // do something with Result
          }
      }
  • 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 (or Push) 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
      }
  • 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.
  • 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.
    • Keep in mind that FString is 16 bytes, FName is 10 bytes, FText is 40 bytes, and uint8 is 1 byte.
    • I only pass by value if the type is > 8 bytes.
      TypeKindMin/Max ValuesSizeExposed to BP
      int8/uint88 bit signed/unsigned integer-128 to 128 - 0 to 2551 bytefalse/true
      int16/uint1616 bit signed/unsigned integer-32,768 to 32,768 / 0 to 65,5352 bytesfalse/false
      int3232 bit signed integer-2,147,483,648 to 2,147,483,6474 bytestrue
      uint3232 bit unsigned integer 0 to 4,294,967,2954 bytesfalse
      int6464 bit signed integer-9,223,372,036,854,775,808 to 9,223,372,036,854,775,8078 bytestrue
      uint6464 bit unsigned integer0 to 18,446,744,073,709,551,6158 bytesfalse
  • 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.
  •  

 

 

until next time