Some Simple Optimizations in Unreal Engine

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

// C++ / Unreal Engine // Published on May 12, 2021, noon

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
      	}
      }
  • 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.
    • I only pass by value if the type is < 8 bytes.
      TypeKindMin/Max ValuesSizeExposed to BP
      int8/uint88 bit signed/unsigned integer+/- 128 - 0 to 2551 bytefalse/true
      int16/uint1616 bit signed/unsigned integer+/- 32,768 - 0 to 65,5352 bytesfalse/false
      int3232 bit signed integer+/- 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,8078 bytestrue
      uint6464 bit unsigned integer0 to 18,446,744,073,709,551,6158 bytesfalse
      FNamesequence of chars12 bytestrue
      FStringsequence of chars16 bytestrue
      FTextsequence of chars24 bytestrue
  • 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.
  • 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.

      TypeKindMin/Max ValuesSize / Net SizeExposed to BP
      FVector_NetQuantizeNormalVector (x,y,z) +/- 112 bytes / 2 bytestrue
      FVector_NetQuantizeVector (x,y,z) - 0 decimal place of precision.+/- 1,048,57612 bytes / 2.5 bytestrue
      FVector_NetQuantize10Vector (x,y,z) - 1 decimal place of precision.+/- 1,677,721.612 bytes / 3 bytestrue
      FVector_NetQuantize100Vector (x,y,z) - 2 decimal place of precision.+/- 10,737,418.2412 bytes / 3.75 bytestrue
      FVectorVector (x,y,z) - 6 decimal place of precision+/- 3.4E+3812 bytes / 12 bytestrue
  • 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.
  • ???

 

 

until next time