Proving Immediate Mode GUIs are Performant
April 30th, 2024
A common internet debate in some circles is Immediate Mode GUI (IMGUI) vs Retained Mode GUI (RMGUI). Game devs in particular are fond IMGUI.
The core argument in favor of IMGUI is that it's super simple and easy. One of the most common arguments against IMGUI is that it's slow and will drain your battery.
This blog post attempts to answer a single question: is IMGUI a battery drain.
My hypothesis is: no, IMGUI is not a battery drain. To test my hypothesis I'm going to measure power draw in a variety of scenarios.
Defining IMGUI and RMGUI
What exactly do "Immediate Mode" and "Retained Mode" mean? Oh boy this is a can of worms. Unfortunately both terms have become confuzzled. They refer to the user API. The implementations can be done in many ways and may or may not contain a wide range of features.
In practice, IMGUI is imperative and RMGUI is declarative.
An IMGUI library will likely compute the entire layout when it needs to render. This is what opponents think is expensive and will drain laptop batteries. Here is Dear ImGui's explanation.
An RMGUI library will likely create a scene graph under the hood. The graph will be updated only when something changes. Microsoft on Immediate vs Retained.
There is no precise or universally agreed upon definition for either IMGUI or RMGUI. Try not to get caught up in semantics.
Dear ImGui (Imperative)
ImGui::Text("Hello, world %d", 123); if (ImGui::Button("Save")) MySaveFunction(); ImGui::InputText("string", buf, IM_ARRAYSIZE(buf)); ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
HTML (Declarative)
<body> <p>Hello, world 123</p> <button onclick="MySaveFunction()">Save</button> <input type="text" id="stringInput" value=""> <input type="range" id="floatInput" min="0.0" max="1.0" step="0.01" value="0.0"> </body>
ReactJS (Declarative)
As provided by a kind Twitter follower.
function App() { const [float, setFloat] = useState(0); const [inputValue, setInputValue] = useState("Quick brown fox"); return ( <div> <div>Hello, world 123</div> <button onClick={() => { console.log("SAVED!"); }} > Save </button> <input type="text" value={inputValue} onInput={(e) => setInputValue(e.currentTarget.value)} /> <div> <input type="range" step={0.01} min={0.0} max={1.0} value={float} onInput={(e) => { const f = Number(e.currentTarget.value) setFloat(f); }} /> <div>Float: {float}</div> </div> </div> ); }
Test Setup
To test the claim that IMGUI is not performant and bad for battery life I'm going to measure power draw. I bought a Shelly smart plug which I connected to a local Home Assistant server. This gives ~1-second precision on power draw which should be good enough.
I'm using "power at the wall" because it's all-inclusive and works the same across operating systems.
Laptops
I have two test laptops. Batteries are fully charged. All background apps are closed to the best of my ability. Displays are set to minimum brightness to achieve lowest possible idle power draw.
- (2021) MacBook Pro w/ M1 Pro
- (2015) Razer Blade w/ Intel i7-6700HQ and Nvidia 1060
Software
I ran a variety of software for ~5 minutes and measured the average power draw. All builds in release mode, of course.
- Immediate Mode
- Dear ImGui
- ImPlot
- RAD Debugger (Windows only)
- EGUI
- Rerun
- Retained Mode
- Spotify (desktop app)
- VSCode
- Scroll and browse code files, but no modifications
- Facebook (Doomscroll)
These tests are fundamentally an apples to oranges test. That's ok! There does not exist a piece of complex software that has been fully implemented both ways.
This experiment gives us a collection of data points. From that we can compare relative costs. It does not have to be comprehensive.
Here are screenshots of what the IMGUI apps look like:
Idle Optimization
An obvious optimization to any IMGUI library is to do zero work if there is no input and no state change. This is trivial for an application to implement.
All tested IMGUI frameworks support "idle optimization". I verified it works and drops the GUI power cost to ~zero. Just like you would expect. If your UI is mostly static then the answer to "is IMGUI a battery drain" is unambigously no.
For today's test I'm most interested in "worst case" performance. Imagine you had a highly dynamic GUI with lots of animations and continuously updating elements. How would that perform? To answer that I disabled all "idle optimizations" such that the IMGUI tests all compute the full layout every frame. If IMGUI is still fast that's a great data point!
Let's Talk about Watts
Before sharing numbers I need to explain what they mean.
My smart plug measures watts. For example the average power consumption over 5 minutes may be 10 watts. Laptop batteries are measured in watt-hours. My MacBook Pro battery has a capacity of 58.2 watt-hours.
If I ran my MacBook at exactly 10 watts it would take 5.82 hours to drain the 58.2 watt-hour battery. If I ran at 20 watts then the battery would die after just 2.91 hours.
The largest laptop battery you'll see is 100 watt-hours. This is the FAA limit for taking a lithium-ion battery on an airplane.
Results — M1 MacBook Pro
Here is the raw data for the M1 MacBook Pro. Each test is separated by a short compile to define a clear boundary.
Here is the average power consumption for each test.
Idle Min Bright 3.5 Max Bright 13.8 Immediate Dear ImGui 7.5 ImPlot 8.9 EGUI 8.2 Rerun 11.1 Retained Spotify 5.8 VSCode 7.0 YouTube 11.5 Facebook 8.7 Other Compiling ~50.0
First, it must be noted that the IMGUI tests are running at a full 120 Hz with zero idle optimizations. This is to simulate a "worst case scenario" of a rich and dynamic GUI. Meanwhile the RMGUI tests are running with idle optimizations.
Let's start with some casual observations:
- Full display brightness costs 10.3 watts, ouch!
- Dear ImGui is really fast
- At 120 Hz it's *barely* slower than VSCode
- Rerun is, somehow, faster than YouTube
- Spotify is lean
- VSCode has way too much jitter, imho
- Compiling at 100% CPU takes 50 watts
Now for the million dollar question. What conclusions can we draw from this data?
I declare that IMGUI performance is pretty gosh darn good! Some readers might have predicted IMGUI to be significantly worse as RMGUI. Instead we see numbers that are in the same ballpark.
Results — Razer Blade (2015)
Now let's take a look at numbers on my 2015 Razer Blade. This is a 9 year old laptop running Windows on an Intel CPU. Windows laptops are notoriously terrible and inefficient.
Here is the average power consumption for each test.
Idle Min Bright 18.0 Max Bright 20.5 Immediate Dear ImGui 27.8 ImPlot 29.7 RAD Debugger 38.0 EGUI 38.6 Rerun 63.2 Retained Spotify 25.6 VSCode 27.3 YouTube 30.9 Facebook 34.8 Other Compiling 74.6
Holy moly this laptop is an inefficient hunk of junk! The new Apple Silicon chips really are quite astonishing.
What can we learn from this? The relative numbers between IMGUI and RMGUI and quite similar to what we saw on the MacBook. Rerun is an outlier that ran poorly for unknown reasons. But otherwise the relative numbers are similar.
This is a second set of data points that once again shows IMGUI is not a significant battery drain relative to RMGUI.
Limitations
My testing has limitations, of course. Measuring "at the wall" is not the same as measuring "at the battery". It's likely that laptops consume more power when operating off a plug than pure battery. I'm also measuring power with a $20 smart plug.
A better test would be to remove the battery and attach a high precision DC power analyzer. Unfortunately such devices cost ~$10,000. I leave this as an exercise for the reader. 😁
Throwing the Gauntlet
A common argument against IMGUI is that it's computationally expensive and will drain batteries. I believe I have proven this claim to be false. Multiple IMGUI implementations with disabled idle optimization are quite performant and in the same ballpark as popular retained mode GUIs.
I challenge anyone who disagrees to provide superior evidence to the contrary. The more data points the better!
Should you use IMGUI or RMGUI?
So, given all this data what does it mean? Should you use IMGUI or RMGUI?
That's way beyond this scope of this post. There are many GUI frameworks and they come with a huge range of trade-offs. There are countless reasons to prefer one over another.
What I believe is that fear of slow performance and bad battery life should not be a reason to avoid IMGUI. I think this post proves that IMGUI is suitably efficient.
Quite frankly, laying out a single screen's worth of elements is not a lot of computation. Modern chips run over 3 billion instructions per second. Layout code is logically complex but computationally easy.
If you are debating Immediate vs Retained then performance and battery life should not be the basis of your choice.
Conclusion
Over the years I've witnessed many debates on IMGUI vs RMGUI. I can't count how many times I've read claims that IMGUI is slow and inefficient. However in all my years I've not once seen a single shred of evidence to support that claim. I wanted to put that claim to the test so I did what I alway do – collect data.
My belief is that modern IMGUI libraries are highly performant and efficient. IMGUI performance and efficiency should be treated as comparable to RMGUI. I believe the presented evidence supports this claim. Therefore anyone making a decision between IMGUI and RMGUI should be base it on factors other than performance and efficiency.
Thanks for reading.
Bonus Thought
I will add that a strong argument against existing IMGUI libraries is styling. Tools like Dear ImGui are spectacular for dev tools. They're not really up to par for most consumer facing GUIs.
Designing a professional quality, general purpose GUI framework is hard. I'd like to think that highly stylizable IMGUI library could exist. However this is an entirely separate discussion from the IMGUI performance debate.