C++ TArray Reference performance slow in 4.8 + 4.9

I am having a performance issue with a TArray of structs, after upgrading from 4.7.5 to 4.8.3.

I’m passing a the reference to the array to a function in a function library, which was working and performing fine in 4.7, but in 4.8 and 4.9, there is a significant hit to performance.

I am a bit new to c++, so if anyone can see anything obvious, please tell me.

The Struct is declared in the same file, while the array is sent from blueprint.

FST_Hex UGameUtilFunctionLibrary::CubeToHex(FVector CubeCoord, const TArray<FST_Hex>& Hexes, int32 GridXCount)
{
	FST_Hex Hex;

	bool Found = false;
	FVector2D OffsetCoords = ConvertCubeToOffset(CubeCoord);
	int32 Index = GetHexIndex(OffsetCoords, GridXCount);
	if (HexIndexValid(Hexes, Index))
	{
		GetHex(Hexes, Index, Found, Hex);
	}

	return Hex;
}

it is good to send the code of the following functions
GetHexIndex and GetHex

int32 UGameUtilFunctionLibrary::GetHexIndex(FVector2D OffsetCoord, int32 GridXCount)
{
int32 GridWidth = (GridXCount - (GridXCount % 2)) / 2 - 1;
int32 Index = GetGridIndex(GridWidth, FMath::FloorToInt(OffsetCoord.X), FMath::FloorToInt(OffsetCoord.Y), true);
Index = Index - (FMath::FloorToInt(OffsetCoord.Y) / 2 * ((GridXCount + 1) % 2));
return Index;
}

void UGameUtilFunctionLibrary::GetHex(const TArray<FST_Hex>& Hexes, const int32 Index, bool &Found, FST_Hex &Hex)
{
	Found = HexIndexValid(Hexes, Index);
	if (Found) {
		Hex = Hexes[Index];
	}
}

bool UGameUtilFunctionLibrary::HexIndexValid(const TArray<FST_Hex>& Hexes, int32 Index)
{
bool valid = (Index < Hexes.Num()) && (Index >= 0);
return valid;
}

I see no issues with this code. but it might be that filling and reading through your array is causing this in other parts of your code.

However I suggests you the following modification. you are using too much functions for a validity check. TArray already has a simple function for it name isValidIndex : TArray | Unreal Engine Documentation

FST_Hex UGameUtilFunctionLibrary::CubeToHex(FVector CubeCoord, const TArray<FST_Hex>& Hexes, int32 GridXCount)
 {
     FVector2D OffsetCoords = ConvertCubeToOffset(CubeCoord);
     int32 Index = GetHexIndex(OffsetCoords, GridXCount);
     
     if ( Hexes.IsValidIndex(Index) )
     {
         return Hexes[Index];
     }
 
     return NULL; // it is safer that Hex as an object
 }

and you can remove two of your functions

I didn’t know that the TArray had that function. Thank you.

I made an isolated test, and problem definitely occurs with the passing of the array. I just don’t understand why this has such a large performance hit in 4.8 and up. I tried just returning the Index insted, and then do the rest in blueprint, which caused no performance hit.

I don’t see anything intensive in this code, you not even interacting with TArray really, atleast not in a way that it would effect performence. How do you know that performance dropped?

You can try to pin point what is taking most time using profiler, which you can find in Window → Session Frontend. In order plug your code in to it first in ProjectName.h (projectname is name of your project) decler stat group

DECLARE_STATS_GROUP(TEXT("Project Name Stats"), STATGROUP_ProjectName, STATCAT_ProjectName)

Now in code you want to messure time of execution you place:

DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Some name of stat that will be displayed"), STAT_ProjectName_NameOFStat, STATGROUP_ProjectName);

You place in specific scope you want to messure, scope is code field that you mark with bracets { } if you place decleration in specific { } it will messure execution time only for code that is in { }. C++ let you create dummy scope (without “if” or “for” etc.) and stat system will acknowledge that, so for example:

void UGameUtilFunctionLibrary::SomeFunction()
 {
    FunctionA();

    {
       DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Some name of stat that will be displayed"), STAT_ProjectName_NameOFStat, STATGROUP_ProjectName);

       FunctionB();
       FunctionC();

    }
 }

Profiler will only messure FunctionB and FunctionC execution time

Also here you got profiler docs:

I suggest you once clean the project and rebuild it

I haven’t made a session profile yet. I will do that now. I have only been using the stat unitgraph for monitoring the framerate. If I ran the function on tick, in 4.7.5 the game frame time was 5ms. In 4.8.3 its 15ms where the is a noticeable spike.

And when you deactivate this function the frame rate is again 5ms?

Hmm i don’t know, you code looks ok, you not looping, you only accessing array which is same as reading varable, so study you code with profiler.

The most silent preference killer is memory allocation (when you spawn or create objects) you should avoid that if doing so in Tick

If you adding items array sometimes TArray need to reallocate the array in memory if array does not fit in specific segment of memory anymore and as i said above memory allocation takes preformence. To prevent that you can reserve memory before hand, best you be maximum value of items that you expect array to have, you can do that with this function:

Thats hints i can give ya ;]

Yes. I even made a side by side camparion with a 4.7.5, 4.8.3 and 4.9.0 builds with the exact same code.
I also tried recreating the function in blueprint, and using the same functions. Somewhat simplified though.

I made a few screenshot for comparison the blueprint setup above. I made the function run on tick (Just for testing), and switched it on/off every 2 seconds. The top one is the “cube to hex c++ function”.
The bottom is the more version where no array reference is passed to c++.

So CubeToHex tames more time or blueprint? keep in mind that blueprint slower then native code

The “blueprint” version is just calling the same c++ functions as the cubeToHex function would. Yes my early test showed me blueprint to be 5-10 times slower. But keep in mind that the one without the spikes, are the “blueprint version”.