Moving FFrameEndSync::Sync() to after the core ticker update

Hey,

I’m sure this question has been asked somewhere before since it seems to have been in the Engine since 4.0 at least and most likely before that as well.

We have several systems that make use of core tickers, e.g. FTSTicker::GetCoreTicker().AddTicker. When looking at profiles in Insights, we noticed that the updates to these systems were happening after the sync point between the game and render threads meaning that there could be an X number of idle ms before their update. When you look at the code, the sync point is very clearly before the core ticker update, with this comment above it “// This could be perhaps moved down to get greater parallelism”.

Questions

  1. Why not move the sync point after the core ticker update as shown in the attached image?
  2. What ticker should systems use if they want to be ticked before this sync point?
    1. Actors or TickableGameObjects are options but they come with some extra constraints that can be a bit frustrating to deal with (e.g. must be an actor), FTSTicker::GetCoreTicker().AddTicker is very useful because it can be used just about anywhere you want.

I made a few assumptions on why this hasn’t been done so wanted to share my thoughts on those up front as below.

  1. It’s too risky.
    1. Absolutely agreed that moving this unconditionally would be an unacceptable risk for existing projects upgrading but, couldn’t you guard it behind a feature flag to allow projects to decide which behaviour they want?
  2. There is no practical impact on the game thread and render thread performance since the core ticker work done after the sync point essentially becomes work done for the next frame.
    1. Absolutely agreed on that however, it would make a difference if systems in the core ticker update schedule async work to be started. Sometimes the game thread can be waiting 5 to 10ms for the render thread to finish depending on the game, this could easily be enough time for async work to be scheduled and potentially even completed in.
  3. Just move systems to use another ticker instead.
    1. While we can do this for systems we own, some of those other tickers come with constraints that make them harder to use (e.g. it must be an actor etc) and additionally, for Third Party plugins e.g. Wwise we’d have to modify their code to make it work which is also a risk and a maintainence burden for each plugin.

Overall, I am mainly just curious to hear the official answer and recommended direction on this, the most compelling reason to do this would be to support faster scheduling of async work, results would vary depending on each game but it could be a 5 to 10 ms faster completion of async work, I appreciate it is a very risky change though.

Insights Example

Red = Where all Core Ticker work is being currently done in the Engine by default, after the long game/render thread sync point.

Green = The time in which async work could be being executed from systems that trigger async work during the core ticker update.

[Image Removed]

Code Snippet

[Image Removed]

[Attachment Removed]

Hi Thomas,

This topic came up recently elsewhere and in general this code is fairly old and has evolved over time instead of being explicitly designed. So I cannot give you an “official” recommendation one way or the other. We will probably make some changes to it as part of the plans to improve frame times and consistency, but we don’t have any specific plans yet. Risk is a big factor and we have talked about some sort of cvar like you mention. We did something similar with s.UseUnifiedTimeBudgetForStreaming in 5.6 which is a similar kind of change that affects frame ordering.

For your practical question, FTickableGameObject could be appropriate and there are also some other global delegates you could subscribe to. There’s no requirement that FTickableGameObjects are actually actors, just that they are associated with a UWorld.

We’re about to start our holiday break, but I will return to this question in January after other internal discussions. I would not expect any change in this area for 5.8, so I would recommend using one of the workarounds you discussed, or possibly modifying the engine yourself. I am curious how that goes if you do try it.

[Attachment Removed]

Thanks for the reply, yes, agreed about FTickableGameObject but again, associating it with a UWorld is also a bit of extra pain if the system you are working with doesn’t really make logical sense to be associated with a world, e.g. online services that live outside of a world. Obviously you can force association and manage changes but it’s extra work.

I’ve got a local change which changes the sync point on a CVar to be after the core ticker update, I’ve also added in Insights profiling bars to the final actions of the game thread as they are sadly absent and just leave a big blank bubble at the end of the game thread.

From my local testing I’ve not noticed any issues for us with the changed sync point and our async work is now being launched 5 to 10 ms sooner. So, I might just go with it and see what happens in the wild.

[Attachment Removed]

If you return the associated world on a FTickableGameObject as nullptr it should run it exactly once per frame even in multiplayer PIE situations so that would make sense for an online services tick probably (although the engine ones do need to tick per world). If the system you describe works out well for you, feel free to submit as a pull request and link this issue.

I’ll follow up on this more after the break, enjoy the holidays!

[Attachment Removed]

Thanks for the info! A number of third party plugins (e.g. Wwise) also use the core ticker as well so I am more of the mind to move the sync point rather than having to modify those plugins, it just makes the overall pacing of the GameThread easier to rationalise about, will see how it goes!

[Attachment Removed]