Speeding up the Unreal Editor launch by … not spawning 38000 tooltips?
If there is one thing the Unreal Engine doesn’t suffer from, it is it’s lack of features. Over the past years it has been transformed from “just” a game engine into an ‘everything machine’.
Games, Movies, Live-Content, VFX, Previz, even virtual fashion shows – it’s a behemoth of an editor with a multitude of tools, most of which are barely used or even known by many of its users. This multitude of features comes with some drawbacks, one of which is the infuriatingly long editor start-up times.
Epic has taken many measures to combat this problem. Live-coding (formerly hot-reloading) aims to eliminate the need to restart the editor, while derived data caches attempt to bypass much of the asset preparation work. There are also many tips that aim to speed things up. For example, using a simplified test level as the editor’s starting point means fewer assets need to be loaded. While all these tips help to some extent, they are, of course, project-dependent and require active work. Wouldn’t it be great if we could reduce startup time in general?
Analyzing the startup performance
That’s why I recently started to dig around in the startup process of the editor, although I didn’t get my hopes up to discover any significant optimization potential in a part of the engine that was surely already well optimized.
At least that’s what I thought until I came about this little function call:
It seems innocent enough, just some widget receiving the text it should use for it’s tooltip, how long could it take? If you are familiar with C++ and some of it’s performance pitfalls you might already have a suspect: Surely there is a string copy involved, right?
Fortunately, Unreal is smart enough not to use raw strings for user-facing text. Instead, the text is passed via an FText struct, which, for the purposes of this article, you can think of as a pointer to text. This makes it quick to copy.
However, there are two other reasons why this function is problematic.
Firstly, despite its name, the function doesn’t just set the text of a tooltip; it spawns a full tooltip widget, including sub-widgets to display and layout the text, as well as some helper objects. This is not ideal from a performance point of view. The other problem?
Unreal does this for every tooltip in the entire editor, and there are a lot of tooltips in Unreal. In fact, up to version 5.6, the text for all the tooltips alone took up around 1 GB of storage space.
The disk size of the tooltip text was reduced in 5.6, but mainly due to the removal of redundant data, not a reduction of the actual number of tooltips. To get an actual number I added a small counter:
The result? When opening a simple project only consisting of the top-down template project, the editor creates around 38000 tooltips. The number can vary based on project settings and enabled plugins, but for ‘real’ projects the number will likely go up even further. (EDIT: Most of those come from optional sources, check the Update at the end of the article for details)
How many of those tooltips are being displayed and read during an average work session? 5? 10? This would mean we spawn 37990 widgets for nothing.
Together, these two problems can result in the editor spending an extremely long time just creating unused tooltips. In a debug build of the engine, creating all of these tooltips resulted in 2-5 seconds of startup time. In comparison development builds were faster, taking just under a second.
Even if we don’t care about the startup time that much, do we want to store almost 40 MB of tooltip widgets in RAM?
The logical follow-up-question is: Can we skip all of those “useless” tooltip creations? The answer is, yes of course, and it takes a surprisingly low amount of code changes to do.
Rather than spawning a new widget as soon as the text is passed, we simply save the text as a new attribute.
The actual widget creation is then moved over to the getter of the tooltip instead:
Runtime Impact
This way, tooltip widgets are only created when and if actually needed. “But wait”, you might say, “didn’t we just move over the work from the startup phase into the actual work phase? Now we have worse performance when actually using the editor!”
You have an argument on paper, but not in practice. To start, it’s highly unlikely that a user would access all 38,000 tooltips in a single session, so we save a considerable amount of time in general. More importantly, there will never be a measurable runtime difference due to the way tooltips work. The editor will never create more than one tooltip per frame, since the cursor can only hover over one widget at a time, and creating a single tooltip is extremely fast. Tooltip creation takes around 0.05ms in debug builds – an amount of time that will never result in a noticeable increase in frame time.
In case you want to test this change yourself, you can find my according pull request here. (Note: You need to be logged in to an Unreal-Linked GitHub account to see the page)
I’ve tried to keep the change as contained and small as possible so hopefully Epic will merge it in the future 🤞
If you found this article interesting to read consider following me on BlueSky, Mastodon or even LinkedIn. Or, if you are old-fashioned, you can even subscribe to my blog further down below and recieve a shiny mail everytime a new post of mine hits the interweb 🙂
Update 1:
Nick Darnell was so curious where all those tooltips were coming from that he checked the tooltip creation on his side. The good news: Most of the tooltip creations come from only two (optional) sources, the project settings panel (around 10,000 tooltips) and the editor preferences (around 10,000). If you don’t have them open by default like me, your number of tooltips will be considerably lower and your editor will start faster. This also means that removing rarely used tools and panels from your editor layout might be valid optimization technique.