zpl.tools and zebrash
Response time, accepted page counts and response size, measured 2026-08-17
zebrash is a local engine: Go library behind a local HTTP wrapper. This page compares it with zpl.tools engine — the same renderer as the API, on localhost. Neither side crosses a network. A local engine is never compared with the hosted zpl.tools API, because the API adds network time that a local call does not carry.
Response time
- zpl.tools engine
- zebrash
Values behind this chart
| Engine | 1 p | 2 p | 3 p | 4 p | 5 p | 10 p | 15 p | 20 p | 25 p | 30 p | 35 p | 40 p | 45 p | 50 p | 75 p | 100 p | 150 p | 200 p | 300 p | 500 p |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| zpl.tools engine | 19 ms | 12 ms | 20 ms | 11 ms | 21 ms | 21 ms | 25 ms | 26 ms | 40 ms | 29 ms | 43 ms | 38 ms | 52 ms | 49 ms | 73 ms | 86 ms | 85 ms | 130 ms | 181 ms | 276 ms |
| zebrash | 36 ms | 66 ms | 93 ms | 90 ms | 137 ms | 265 ms | 434 ms | 578 ms | 713 ms | 814 ms | 941 ms | 1,090 ms | 1,225 ms | 1,392 ms | 2,134 ms | 2,940 ms | — | — | — | — |
On one page, zpl.tools engine answers in a median 19 ms and zebrash in 36 ms, a factor of 1.9. At 100 pages, the largest packet the benchmark sent to both engines, the medians are 86 ms and 2,940 ms. zpl.tools engine rendered 500 pages in a median 276 ms, and 500 pages is where the benchmark stopped — the renderer sets no page limit. The benchmark measured zebrash to 100 pages.
Response size
- zpl.tools engine
- zebrash
Values behind this chart
| Engine | 1 p | 2 p | 3 p | 4 p | 5 p | 10 p | 15 p | 20 p | 25 p | 30 p | 35 p | 40 p | 45 p | 50 p | 75 p | 100 p | 150 p | 200 p | 300 p | 500 p |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| zpl.tools engine | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB | 87.0 KB |
| zebrash | 10.5 KB | 18.0 KB | 28.4 KB | 39.0 KB | 57.3 KB | 125.9 KB | 199.7 KB | 274.9 KB | 343.3 KB | 399.9 KB | 461.5 KB | 538.4 KB | 611.2 KB | 684.5 KB | 1.02 MB | 1.40 MB | — | — | — | — |
A one-page PNG from zpl.tools engine is 87.0 KB. At 500 pages the same response is 87.0 KB: 1.0 times the size for 500 times the pages. At 100 pages, the response is 87.0 KB against 1.40 MB from zebrash.
Corpora
Each corpus is one request body, repeated at every page count. A corpus reaches this page only where the run measured both engines on it.
Simple label
Labelary's own example label, duplicated per page. Every page carries the same label.
| Pages | zpl.tools engine p50 | zpl.tools engine p90 | zpl.tools engine p95 | zebrash p50 | zebrash p90 | zebrash p95 |
|---|---|---|---|---|---|---|
| 1 | 20 ms | 22 ms | 23 ms | 51 ms | 52 ms | 52 ms |
| 10 | 27 ms | 28 ms | 28 ms | 278 ms | 283 ms | 284 ms |
| 50 | 47 ms | 49 ms | 49 ms | 1,318 ms | 1,332 ms | 1,347 ms |
| 100 | 66 ms | 69 ms | 70 ms | 2,663 ms | 2,701 ms | 2,703 ms |
| 500 | 195 ms | 206 ms | 208 ms | — | — | — |
GS1 graphics label
A real retailer label with embedded ~DG graphics, duplicated per page. Every page carries the same label.
| Pages | zpl.tools engine p50 | zpl.tools engine p90 | zpl.tools engine p95 | zebrash p50 | zebrash p90 | zebrash p95 |
|---|---|---|---|---|---|---|
| 1 | 17 ms | 18 ms | 18 ms | rejected | rejected | rejected |
| 10 | 32 ms | 32 ms | 33 ms | rejected | rejected | rejected |
| 50 | 46 ms | 47 ms | 47 ms | rejected | rejected | rejected |
| 100 | 63 ms | 93 ms | 166 ms | rejected | rejected | rejected |
| 500 | 143 ms | 147 ms | 147 ms | — | — | — |
Orderful style
Orderful retail compliance labels, a different label on every page. Every page carries a different label, so no engine can reuse a page it already rendered.
| Pages | zpl.tools engine p50 | zpl.tools engine p90 | zpl.tools engine p95 | zebrash p50 | zebrash p90 | zebrash p95 |
|---|---|---|---|---|---|---|
| 1 | 19 ms | 20 ms | 20 ms | 36 ms | 45 ms | 45 ms |
| 10 | 21 ms | 25 ms | 25 ms | 265 ms | 268 ms | 268 ms |
| 50 | 49 ms | 58 ms | 63 ms | 1,392 ms | 1,417 ms | 1,422 ms |
| 100 | 86 ms | 87 ms | 88 ms | 2,940 ms | 2,974 ms | 2,982 ms |
| 500 | 276 ms | 280 ms | 280 ms | — | — | — |
SPS Style
The same retailer labels delivered as ~DG bitmap pages, the SPS Commerce way. Every page carries a different label, so no engine can reuse a page it already rendered.
| Pages | zpl.tools engine p50 | zpl.tools engine p90 | zpl.tools engine p95 | zebrash p50 | zebrash p90 | zebrash p95 |
|---|---|---|---|---|---|---|
| 1 | 13 ms | 13 ms | 13 ms | 62 ms | 64 ms | 66 ms |
| 10 | 36 ms | 40 ms | 40 ms | 431 ms | 437 ms | 439 ms |
| 50 | 68 ms | 76 ms | 77 ms | 2,014 ms | 2,042 ms | 2,066 ms |
| 100 | 104 ms | 106 ms | 106 ms | 4,102 ms | 4,138 ms | 4,145 ms |
Method
k6 drives every point, one request at a time, 10 iterations per point, 5 past 150 pages. A one-page point carries 10 samples.
Client: k6, one request at a time. Both hosted engines measured in the same session from a Hetzner ccx13 (dedicated vCPU) in the fsn1 datacenter, Germany — network RTT included, the same for both sides. Local engines measured on a MacBook Pro (Apple Silicon), no network crossed; the zpl.tools, zebrash, BinaryKits and Neodynamic summaries carry over from the 2026-08-09 run, because no code in the render path changed between them. labelize was added on 2026-08-17 and measured on the same MacBook, at one page only — see below.
Measured 2026-08-17. The harness is benchmarks/run.sh, and its header names the local servers each engine needs. To repeat the run:
./benchmarks/run.sh
node scripts/distill-benchmarks.mjs
node scripts/generate-performance-pages.mjsThe first command measures, the second writes website/lib/performance-data.json, and the third writes this page from it.