Benchmarks Web maps
MapLibre GL JS vs Mapbox GL JS
The fork measured against the code it forked from. mapbox-gl 1.13.3, the last BSD-licensed Mapbox release, beside maplibre-gl 5.24.0 and 6.7.0, all three fed the same self-hosted Berlin tiles, style, sprites and glyphs in one pinned Chromium on one GPU: startup timeline, frame time cold and warm, memory. I've committed code to MapLibre. The runner's public; check my numbers.
Startup timeline
From script tag to a drawn map of Berlin Mitte at z11, read from the events every build fires: bundle import, style load, first tile, load, first idle. The headline mark is load with fades off, because the builds disagree about whether a fade counts. Plus 5,000 GeoJSON polygons added to the settled map.
Test 1 Load, fades off
Map constructed until the load event with fadeDuration 0: style, every tile in the view fetched and parsed, everything drawn once. The one startup mark that means the same amount of work in all three builds. (LOWER ms = FASTER)
Test 2 Bundle import
Script or module tag inserted until the library has evaluated, from the loopback server. (LOWER ms = FASTER)
Test 3 Style load
Map constructed until the style.load event: style JSON fetched and parsed, layers created. (LOWER ms = FASTER)
Test 4 First tile
Map constructed until the first vector tile has been fetched and parsed by a worker. (LOWER ms = FASTER)
Test 5 Load, default fade
Map constructed until the load event with the default 300 ms fade. mapbox-gl 1.13 holds this event until its tile fade has finished; the MapLibre builds do not, so this mark is what each build reports, not the same amount of work. (LOWER ms = FASTER)
Test 6 First idle, default fade
Map constructed until the first idle event after load with the default 300 ms fade. mapbox-gl 1.13 also holds idle until its label fade has finished; the MapLibre builds fire idle within a few milliseconds of load. (LOWER ms = FASTER)
Test 7 GeoJSON, 5,000 buildings
A pre-parsed FeatureCollection of 5,000 Berlin building footprints added as a fill layer to the idle map, until the next idle. (LOWER ms = FASTER)
Mapbox GL JS 1.13 drew the first complete frame in 193 ms on average; MapLibre GL JS 6 took 351 ms (1.82x)
Frame time
One deterministic camera path over Berlin, traversed twice: cold, waiting for idle at each of the views so tiles are fetched and parsed on the way; then warm, moving the camera every frame with everything resident.
Test 1 Cold pan, per view
Mean time per view over the 60-view camera path, each view advanced only when the map reports idle, so fetch, parse, layout and paint are all inside it. (LOWER ms = FASTER)
Test 2 Warm paint, per frame
Mean time per frame over the 600-step camera path, the camera moved on every render event, vsync and the frame-rate limit off. Every tile the cold pan loaded is resident; tiles only needed at intermediate camera states are still requested and abandoned as the camera moves on, and those requests are counted beside the timings and included in the frame time. (LOWER ms = FASTER)
MapLibre GL JS 5 painted a warm frame in 3.06 ms on average; Mapbox GL JS 1.13 took 6.53 ms (2.14x)
Memory
Private bytes of the renderer and GPU processes, fresh browser process per sample, minus a blank-page baseline.
Test 1 After first idle
Renderer and GPU process memory over a blank page, once the initial view has settled. (LOWER MB = FASTER)
Test 2 After the camera path
Renderer and GPU process memory over a blank page after the 60-view cold pan, with each library's default tile cache. (LOWER MB = FASTER)
MapLibre GL JS 5 held 919 MB over baseline after the camera path; Mapbox GL JS 1.13 held 1448 MB (1.58x)
Test conditions: rig, corpus, protocol
- Machine
- BOSGAME EffiZen ARB19B mini PC
- Chip
- AMD Ryzen 7 6800H with Radeon Graphics
- Cores
- 8 cores / 16 threads
- Memory
- 24 GB at 4800 MT/s
- OS
- Microsoft Windows 11 Pro 24H2 (build 26100) (x64)
- Runtime
- Playwright 1.62.1 Chromium 151.0.7922.34 headless shell; Bun 1.4.0 harness and loopback static server
- GPU
- AMD Radeon 680M, integrated in the Ryzen 7 6800H, driver 32.0.12033.1030 (2024-11-26)
- Browser
- Chromium 151.0.7922.34, ANGLE (AMD, AMD Radeon(TM) Graphics (0x00001681) Direct3D11 vs_5_0 ps_5_0, D3D11); flags --use-angle=d3d11 --use-gl=angle --ignore-gpu-blocklist --disable-gpu-vsync --disable-frame-rate-limit --disable-background-timer-throttling --disable-renderer-backgrounding
- Display
- 1280x800 at 1x, offscreen; 1920x1080 at 59 Hz attached
- Warmups
- 2 unmeasured passes
- Measured
- 15 passes per tool
- Process
- One Chromium process for startup and frame time, fresh browser context per pass, candidates interleaved and rotated each pass; 15 memory samples with a fresh browser process each; every request outside loopback fails at the resolver and any attempt invalidates the run
- Cache
- Fresh context per pass, no HTTP cache; timing passes run with maxTileCacheSize 10000 so the warm traversal is warm, memory samples with each library's default tile cache; tiles served gzip from one MBTiles by the harness
- Output
- The map's own events timed with performance.now() under cross-origin isolation; Playwright screenshots and queryRenderedFeatures for the parity gate; process memory from the OS
What did we learn?
MapLibre GL JS 5 painted a warm frame in 3.1 ms to Mapbox GL JS 1.13's 6.5 ms and held 919 MB to its 1,448 MB after the same camera path; 1.13 drew its first complete frame in 193 ms to MapLibre's 335, starting six workers to MapLibre's one.
| Candidate | Startup timeline (ms, lower is better) | Frame time (ms, lower is better) | Memory (MB, lower is better) |
|---|---|---|---|
| Mapbox GL JS 1.13 | 192.7 ms | 6.5 ms | 1448.4 MB |
| MapLibre GL JS 5 | 334.8 ms | 3.1 ms | 918.6 MB |
| MapLibre GL JS 6 | 351.1 ms | 3.2 ms | 1004.6 MB |
Five years after the fork, the renderer is where MapLibre pulled ahead: with every tile resident and the camera moving every frame, both MapLibre builds paint a frame in about half the time of the code they forked, and MapLibre 5 holds a third less memory after the same path. Startup went the other way, and for a reason that is in the defaults rather than the engine: mapbox-gl 1.13 starts six Web Workers on this 16-thread machine and parses the initial tiles in parallel, while MapLibre restricted its default to one worker, so 1.13 reaches its first complete frame in 193 ms to MapLibre's 335 and 351 with fades off. The default-fade marks tell a different story only because 1.13 holds its idle event until its 300 ms label fade ends and MapLibre does not. The cold pan, which is fetch, parse, layout and paint per view, ordered the three the other way round from the warm paint: MapLibre 6 fastest at 592 ms per view, 1.13 at 631, MapLibre 5 at 700. MapLibre 6 costs about 9 percent more memory and 4 percent more per warm frame than MapLibre 5, and that is its new default of overscaling four zoom levels past the source's maxzoom, which also raises its tile requests per pass to 374 against MapLibre 5's 225. The scorecard below has the section means; the limitations say what these numbers do not mean.
What this does not prove
- One GPU, one operating system, one browser build: an integrated AMD Radeon 680M on Windows 11 24H2 in Playwright's Chromium 151 headless shell through ANGLE on Direct3D 11, driven over ssh from a session with no desktop. Ratios on Apple silicon, on a discrete NVIDIA card, or in Firefox will differ.
- Renderer only. Mapbox's hosted data, the Standard style, its CDN and its current SDK are not measured, because Mapbox's Product Terms forbid using current Mapbox GL JS in a comparison with a substitute. The Mapbox candidate is the last BSD-licensed release, which is also the code MapLibre forked, so this page compares the fork with its origin, not with the current Mapbox product.
- Defaults are measured, not equalized. mapbox-gl 1.13 starts six Web Workers on this machine and the MapLibre builds start one, which is most of the difference in load time with fades off and in renderer memory. MapLibre GL JS 6 defaults zoomLevelsToOverscale to 4, so past the source's maxzoom of 14 it fetches and parses each parent tile once per child position, which is why its tile requests per pass are higher and its label placement at high zoom differs; the parity gate counts distinct features on screen so that choice does not fail it.
- The builds disagree about what load and idle mean. mapbox-gl 1.13 holds load until its tile fade and idle until its label fade have finished; the MapLibre builds fire idle within a few milliseconds of load. The default-fade marks are reported as each build fires them, and the section's headline uses load with fadeDuration 0, the one mark that means the same amount of work in every build.
- One style, OSM Bright, with no globe, terrain, hillshade or 3D buildings, so features one build has and another lacks are not exercised. The style names an icon per POI class and its sprite lacks some of them, so every build logs the same missing-image warnings and draws those labels without an icon.
- Frame time is unthrottled and headless: relative renderer cost per frame with the camera moving every frame, not what a 60 Hz screen shows a user. The warm traversal has every tile the cold pan loaded resident, but tiles only needed at intermediate camera states are still requested and abandoned as the camera moves on; those requests are counted beside the timings and are inside the frame time.
- The GeoJSON test is sensitive to protocol details in a way the other tests are not: mapbox-gl 1.13's figure moved from 311 ms to 404 ms between two runs on the same day whose only difference was a two-second settle before startup, while both MapLibre figures stayed put. The published number is from the run with the settle; treat that test as indicative.
- Memory is renderer plus GPU process private bytes over a blank page, with each library's default tile cache; on an integrated GPU the GPU process figure is system memory. The JS heap metric covers the main thread only, because the browser build does not expose the API that includes workers, and tile data lives in the workers. A fresh headless browser in this non-interactive session occasionally dies at launch or under load, for any candidate; a dead browser was relaunched and the sample redone, and the relaunch counts are in the run file.
- mapbox-gl 1.13.3 renders through WebGL 1 and the MapLibre builds through WebGL 2, because that is what each artifact does; nothing was patched to equalize it.
- Fifteen timing passes and fifteen memory samples on one machine on one day, the timing and memory phases run as separate processes; every sample is in the run file.
Rerun it yourself: the runner and configuration are in the repository, and the methodology page covers what every run holds constant. Think a result is wrong? Open an issue with your rig and your samples.
Mapbox and Mapbox GL JS are trademarks of Mapbox, Inc. mapbox-gl 1.13.3 is used under its BSD-3-Clause license. MapLibre is a project of the MapLibre organization; maplibre-gl is BSD-3-Clause. Map data: OpenStreetMap contributors, ODbL, via the Geofabrik Berlin extract of 2026-01-01, cut with planetiler. Style: OSM Bright by OpenMapTiles, BSD-3-Clause. Glyphs: Noto Sans, SIL Open Font License.