docs v1.0.0

Performance

bibui has two routes, and you choose one per app. Silk + Skia is a GLFW window drawn by SkiaSharp on the GPU. Wayland + managed is a raw Wayland window, drawn on the CPU by bibui's own rasterizer. This page gives the results of measuring them. Every number comes from one machine. Compare the routes with each other, not with your own hardware.

5.2MBThe showcase app, Native AOT, one file
9.9msSpawn to first frame, small dashboard
33.1MBResident memory at the first frame
0.01msTo present a Wayland frame (0.01 to 0.03 ms)

The machine. Ryzen AI 9 365 with a Radeon 880M, Hyprland, .NET 10. Each timing ran on a core with nothing else on it. The headline figures are for the Wayland + managed route.

How small

The same small dashboard app, built on each route and published with Native AOT:

  • The showcase publishes to about 5.2 MB. It is an observatory console that uses most of bibui: charts, Markdown, dialogs, theme files and a seeded theme.
  • Your own code adds little. Most of the binary is bibui and the managed rasterizer, so it grows slowly with the app. System.Text.Json, for example, adds about half a megabyte.
  • The Wayland + managed route takes no third-party NuGet packages. Bibui.Platform.Wayland and Bibui.Raster.Managed.Backend depend on nothing outside bibui.

How fast it starts

The showcase was measured as a Native AOT binary on the Wayland + managed route. It reaches its first rendered 1440 × 900 frame about 29 ms after it is spawned.

How much memory

Most nodes come from pools, so building a frame allocates almost nothing. A pooled box is the n-th box from a pool, and the pool rewinds when the frame ends. A retained box is the same instance from frame to frame.

Frame times

A 600-button grid, built, laid out and rasterised headless. These rows exclude presenting.

In a window

  • Presenting. A Wayland frame goes into a shared-memory buffer, so presenting costs 0.01 to 0.03 ms. There is no GL upload or readback. On the GPU path, the CPU side of a 600-button frame is about 5.5 ms p50. That covers recording the frame and flushing it.
  • Pacing. Wayland frame callbacks give an even 16.67 ms interval. The GLFW path's p99 interval is 17.5 ms.
  • Idle. BeginFrame sleeps until input arrives or something asks for a frame. When every animation has arrived, the loop goes idle.

Full redraws: where the GPU wins

A CPU-only route still holds 60 Hz because of damage tracking. A typical frame changes only a hover colour or a caret. Only the rectangles that changed are rasterised. The output is byte-identical to a full repaint.

Configurations and systems

Every route on every machine we have measured so far. This table grows as more machines are measured. A blank cell has not been measured.

SystemRouteAOT sizeFirst frameRSSGrid p50 1800×1400
Ryzen AI 9 365
Radeon 880M · Hyprland
Wayland + managed5.8 MB9.9 ms33.1 MB4.61 ms
Ryzen AI 9 365
Radeon 880M · Hyprland
Silk + Skia15.1 MB17.1 ms46.8 MB6.37 ms
Intel laptop, integrated GPUboth routes————
Desktop, discrete GPUboth routes————
Low-power x64 (N100 class)Wayland + managed————
Arm64 boardWayland + managedOnce Linux arm64 is supported

Measuring your own app

BIBUI_PROFILE=1 prints, for every frame, the time EndBuild spent building, laying out and recording. Drawing and presenting happen in your own loop, so time those yourself:

C#
while (WaylandWindow.BeginFrame(window, runtime))
{
    long t0 = Stopwatch.GetTimestamp();
    runtime.BeginBuild(window.Events);
    BuildUi(runtime);
    runtime.EndBuild();                                  // build + layout + record

    long t1 = Stopwatch.GetTimestamp();
    WaylandWindow.BeginDraw(window);
    managed.Draw(runtime.DrawList);                      // raster

    long t2 = Stopwatch.GetTimestamp();
    WaylandWindow.EndDraw(window);                       // present: swap or commit
    long t3 = Stopwatch.GetTimestamp();

    Log(Stopwatch.GetElapsedTime(t0, t1), Stopwatch.GetElapsedTime(t1, t2), Stopwatch.GetElapsedTime(t2, t3));
    WaylandWindow.EndFrame(window, runtime);
}
  • Run on a quiet machine and record the load average with the numbers. On a busy machine the medians inflate and p99 becomes mostly scheduler noise.
  • BIBUI_ALWAYS_RENDER=1 draws a frame on every refresh. Use it for steady-state timing of a scene that would otherwise go idle.
  • The numbers above come from the maintainers' benchmark harness, which is not in the public repository.