How to know which assets are being loaded by async IO tasks?

We’re having a weird issue with UNavigationSystemV1::OnWorldPostActorTick occasionally blocking the game thread until the current async pathfinding query completes… but it doesn’t even start for up to 50ms, since all of the Background Workers are filled with async I/O tasks.

I’ve tried using Asset Loading Insights, using channels
default,counter,stats,file,loadtime,assetloadtime,task,RHICommands,RenderCommands,Object,Metadata,Asset Metadata
and Named Events enabled on the running build; but still, the async load thread track seems to show only what’s being loaded from the game thread. Our async I/O tasks seem to be generated by the Render Thread, and mostly, but not only, by FDistanceFieldSceneData::AsyncUpdate calls that take much longer than others. I’ve even tried profiling directly from the Editor, hoping that those labels would be replaced with asset names, but they only got replaced with an “ExecuteTask” label.

Is there a way to inspect the loading cost of different assets? We have hundreds of assets and a quite short time for optimization. Working on assets optimization without knowing which assets are being more expensive would take months to fix this issue.

Also, it would be nice to understand what’s causing so many assets to start being loaded in a single frame: it doesn’t seem to be associated to level streaming nor to something spawning, so we don’t have a clue on why this is happening or how we could avoid this.

You can’t exactly measure the specific I/O cost as files can be in many places on the target drive & file transfers are the slowest operation when it comes to retrieving data.

You can use the size map to narrow down the largest assets and consider some form of optimizations for them

The size map != load time though, just a rough estimate of the weight of the asset that can increase load times.

Otherwise you could for instance have an array of objects and load them all up and save the microtime difference between the start and end of the loading. You could put the output into a struct and then sort it via asset load time and save it to a file.

Depending on the CPU side you might also just not have enough free resources to process the assets in a timely manner.

You could also consider moving I/O tasks into an FRunnable separate thread that could offload any file transfer blockage.

Looking into the source code that drives the FGenericBaseRequest that is called during the io thread creation it does have a delegate called on completion:
FAsyncFileCallBack* CompleteCallback passed in as one of the parameters.

In theory you could hook into this callback delegate and use it’s call to know what I/O async process finished. Though it may require some custom inherited classes to expose the delegate

At a glance the GenericPlatformFile.cpp seems to be responsible for producing the I/O background tasks, though no completion delegate is exposed for custom hooks.

Which function are you using to invoke the I/O operation? Check if it has a completion delegate built in.