Emacs SVG Benchmark Reveals Gaming-Caliber Frame Rates

Emacs SVG Benchmark Reveals Gaming-Caliber Frame Rates

Emacs SVG Benchmark Reveals Gaming-Caliber Frame Rates

1. TLDR

Rendering an Emacs status bar as an SVG image, like with my svg-line package (demo), really is several times heavier than the native text engine. The skeptics were right. But it doesn't matter in practice: even a fully-repainted 3000-pixel ultrawide bar holds ~77 fps, and a thin inline animation rasterizing a fresh frame every tick runs at 130–520 fps. To put that into perspective, this is still well above the 60 fps that high-frame-rate games target. More than enough for a text editor if you ask me!

I got two real take-aways from this benchmark. First, the rendering cost of SVG scales with the SVG's pixel area, not with what's drawn on it (text and icons cost the same as you'll see). Second, in any real Emacs config the bottleneck is the content query (I'm looking at you, git), not the renderer (I'm looking at you, SVG, with doe eyes).

2. Why Benchmark This?

The skepticism is reasonable. Emacs is optimized for a small number of cached images, while a status bar that changes on every redisplay can generate a new image many, many times per second. Intuitively, that should be slow.

But other Emacs SVG projects show smooth 60 fps animation, as one redditor pointed out, and anecdotally svg-line felt fast on my end.

I felt compelled to measure it, so I did, with a self-contained harness (to borrow the redditor's term) that runs in bare emacs -Q.

3. Results: SVG vs Native Text Engine

You can run the benchmark yourself, in a bare emacs -Q or your own Emacsen. My setup is Emacs 31.0.50 (native-compiled), librsvg (linked into my build), on a 2025 M5 Mac running Tahoe 26.1.

Three renderers draw the same thing at three widths (a narrow split, a normal window, and an ultrawide monitor): an SVG bar of text, an SVG bar of Nerd-Font icon glyphs, and (as the baseline) the native text engine drawing the same characters.

3.1. SVG Rendering Text

SVG / text mean ms min ms max ms mean fps min fps
narrow 800px, 65c 3.44 3.09 3.96 290 253
medium 1800px,149c 7.49 6.98 7.91 134 126
large 3000px,249c 12.91 12.39 13.37 77 75

3.2. SVG Rendering Icons

SVG / icons mean ms min ms max ms mean fps min fps
narrow 800px, 65c 3.60 3.37 3.80 278 263
medium 1800px,149c 7.85 7.47 8.53 127 117
large 3000px,249c 13.31 12.67 14.10 75 71

3.3. Built-in Rendering Text

BUILT-IN / text mean ms min ms max ms mean fps min fps
narrow 800px, 65c 0.16 0.13 0.23 6377 4378
medium 1800px,149c 0.21 0.19 0.37 4789 2686
large 3000px,249c 0.26 0.24 0.33 3842 3046

3.4. Analysis

SVGs perform at all widths. Even the large 3000-pixel bar (an ultrawide-monitor width), repainted every frame, stays above 60 fps: ~77 fps for text and ~75 fps for icons, and the worst trial across the run is still 71 fps.

Text and icons cost essentially the same (290 vs 278, 134 vs 127, 77 vs 75). The cost is dominated by rasterizing the SVG as a whole, so what is painted on it doesn't seem to matter. The relevant correlate is the SVG's pixel area.

The native engine is ~20–50× faster. The skeptics are right that SVG is heavier because a monolithic image is re-rasterized in full whenever any pixel changes, while the text engine updates incrementally from cached glyphs, and the gap widens with width for that reason. But!!! This doesn't matter in practice. Even in the worst case, SVG never drops below the gaming gold standard, which is more than enough for a text editor.

4. A Live Inline Animation

Why talk frame rates without an animation? Here, three purple icons (Emacs, Org, Image) glide back and forth along a thin white bar, measured across three widths.

line animation mean ms min ms max ms mean fps min fps
small 300x56px 1.94 1.77 2.26 517 443
medium 700x56px 4.03 3.75 4.55 248 220
large 1400x56px 7.45 7.04 7.77 134 129

An inline animation rasterizing a fresh SVG every frame runs at 130–520 fps, and the worst trial is still ~129 fps.

NOTE: My screen recording can't convey the real frame rate because my screen-capture tool is capped at 60 fps.

Run the demo yourself (below) to see the true smoothness.

5. How I Measured It

The harness renders a status-bar-shaped image (or, for the baseline, native text) into a buffer, re-renders it every frame, forces a redisplay, and times the wall-clock per frame in milliseconds (which the tables also report as fps).

(defun svg-bench--time (render-fn reps)
  "Render REPS frames, forcing redisplay, and return ms per frame."
  (let ((gc-cons-threshold most-positive-fixnum))
    (clear-image-cache)
    (funcall render-fn 0) (redisplay t)             ; warm-up, untimed
    (let ((t0 (float-time)))
      (dotimes (i reps) (funcall render-fn (1+ i)) (redisplay t))
      (/ (* 1e3 (- (float-time) t0)) reps))))

Some details on what makes this demo reflect practical use of SVG in Emacs:

  • I ran it in a real GUI frame. If you ran this with --batch or emacs -nw there's no display and rasterization is skipped.
  • I set gc-cons-threshold during timing and I discard a warm-up frame (because the first render loads librsvg).
  • I force a miss in Emacs's image cache. Emacs caches each rasterized image by its full data string, so if a frame's string repeats, the cache serves the bitmap and the benchmark measures a cache hit rather than a real render. Rotating through a small pool of content (say ten icons, or a short alphabet) repeats every few frames. So each frame in my benchmark carries a 2px per-frame colour marker (a tiny non-text rectangle) that makes the whole-image data string unique and guarantees a genuine re-rasterization.

6. In Practice, the Bottleneck Is Not the SVG

The benchmarks above use synthetic content precisely so they measure the rendering, not the performance of any indicator you might put in a status bar.

In a real configuration, a single synchronous git query in the version-control segment runs ~7.6 ms, which is on the order of painting the entire SVG bar, and far more than rendering the one segment it occupies. A native mode line showing the same version-control state would pay this cost too.

When an Emacs status bar feels slow, an I/O-bound content function is almost certainly the reason.

7. Conclusion

SVG status bars are several times more expensive than the native text engine — and also far faster than 60 fps, even at ultrawide widths. The native engine keeps an order of magnitude in reserve, but SVG has plenty for the job, and in practice the slow part of a status bar is the content it queries, not the engine that paints it.

8. Run it on Your End

The harness is one self-contained file, svg-bench.el, in my Emacs configuration repository (built-in svg and cl-lib only). In a graphical Emacs:

emacs -Q svg-bench/svg-bench.el

Then M-x eval-buffer, and run any of the demos — svg-perf-text, svg-perf-icons, builtin-perf-text, svg-perf-animation. The full report comes from svg-perf-benchmark (the three status-bar tables) and svg-perf-animation-benchmark (the animation table).