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.

Software

I ran a variety of software for ~5 minutes and measured the average power draw. All builds in release mode, of course.

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:

Dear ImGui + ImPlot EGUI RAD Debugger Rerun

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.

Raw Power Data

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:

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.