Skip to content

emikra/typst-bench

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

typst-bench

Companion benchmark + demo repository for the Emikra Labs article "Choosing Typst Over LaTeX for PDF Generation in DeedTracker."

This repository quantifies and demonstrates the tradeoffs discussed in the article: compile speed, engine/binary footprint, and output characteristics for resume-style PDF generation, comparing Typst against LaTeX (via Tectonic, the same engine DeedTracker used before migrating).

It is intentionally small and reproducible: a single Go binary drives the benchmarks, and a Docker Compose stack pins both engines so results are comparable across machines.

What's inside

Path Purpose
internal/bench Engine abstraction + benchmark harness (engine-agnostic).
internal/cli Small helpers (repo-root discovery, fatal errors).
cmd/bench CLI runner. Benchmarks locally or launches Docker.
cmd/demo Generates a sample PDF from the Typst template (a real demo).
templates/resume Vendored minimal.typ (from DeedTracker) + minimal.tex (LaTeX comparison) + raw.typ (static Typst, no injection).
templates/sample/resume.json Shared resume data injected into the Typst template.
Dockerfile, docker-compose.yml Reproducible, version-pinned engine images.

Design

  • An Engine interface (internal/bench) hides the differences between Typst and LaTeX behind a single Compile(ctx, outPath) method that returns the elapsed time.
  • TypstEngine shells out to typst compile, injecting the resume JSON via --input resume=<json> (the template reads json(bytes(sys.inputs.resume))).
  • LaTeXEngine shells out to tectonic, which performs the multi-pass compilation automatically (mirroring the old DeedTracker LaTeX path).
  • TypstRawEngine compiles a fully static Typst document (raw.typ) with no JSON injection and no control flow, isolating plain Typst compilation cost — a like-for-like counterpart to the static minimal.tex.
  • A small harness runs a warmup phase (to absorb first-run / JIT / package-fetch costs) followed by N timed iterations, then aggregates min / median / mean / max and the output PDF size. It also reports each engine's on-disk binary size.

Benchmark methodology

  • Document: a representative minimal one page resume. The same content drives the Typst template via injected JSON; the LaTeX template is a comparable self-contained .tex; raw.typ is the fully-expanded Typst equivalent with no injection, used by the typst-raw engine .
  • Metrics:
    • Compile time: min / median / mean / max over N timed iterations (after warmup).
    • Engine binary size (proxy for container/footprint cost).
    • Output PDF size.
    • Peak memory: peak resident set size (RSS) of the engine process during compile, sampled from /proc/<pid>/status (VmHWM). Linux-only; on other platforms it reports 0. This captures the engine's own working set, not the Go runner's.
    • Default accessibility (Typst emits tagged/UA-friendly PDFs by default; LaTeX requires configuration).
  • Reproducibility: engine versions are pinned in the Docker image. Host runs use whatever is on PATH. Warmup isolates cold-start/package-fetch effects from steady-state timing.

Results

Results for a minimal one-page resume (30 iterations after 3 warmup runs).

Engine BinSize Mean Median Min Mem(med) Mem(max) PDFSize
typst 53.2 MB 20.333ms 20.389ms 19.36ms 29.9 MB 29.9 MB 40.8 KB
typst-raw 53.2 MB 19.823ms 19.672ms 18.443ms 29.5 MB 29.6 MB 39.6 KB
latex 53.2 MB 929.492ms 823.36ms 782.631ms 217.0 MB 219.6 MB 27.4 KB

Key observations:

  • Compile speed: Both Typst engines compile in ~20ms, while LaTeX (Tectonic) takes ~820ms median — a 40× difference. The multi-pass .aux cycle and TeX Live package operations are the main contributors.
  • Memory footprint: Typst peaks at ~30 MB RSS, compared to LaTeX's ~220 MB. The huge difference is Tectonic's bundled TeX Live engine state (font/metric caches, package loading, hyphenation tables).
  • Injection overhead: Comparing typst (JSON injection + loops) vs typst-raw (static) shows negligible overhead — roughly 0.5ms and ~0.4 MB in memory. Typst's dynamic features are effectively free for resume-sized documents, meaning you get the full power of data-driven templates without a meaningful performance penalty.
  • PDF size: Typst produces slightly larger PDFs (40 KB vs 27 KB) due to tagged/accessible PDF metadata by default. LaTeX requires explicit configuration for comparable accessibility, so the smaller file reflects a less standards-compliant output.

Docker strategy

  • Dockerfile builds the Go binaries and installs pinned engine versions:
    • Typst v0.15.0 (x86_64-unknown-linux-musl)
    • Tectonic 0.16.9 (x86_64-unknown-linux-gnu)
    • DejaVu fonts (used by the Typst template) + fontconfig.
  • docker-compose.yml defines a bench service that mounts the repo and runs the benchmark.
  • The Go runner orchestrates the stack: with --docker, it shells docker compose run --rm (setting TYPST_BENCH_IN_DOCKER=1 to avoid recursion), runs the benchmarks inside the container, streams the report to the host, then tears the container down.

Usage

# Simplest approach using docker-compose
docker compose up

# Local (requires `typst` and `tectonic` on PATH)
go run ./cmd/bench --engine all --iterations 30
# Writes ./out/typst.pdf and ./out/latex.pdf for inspection
go run ./cmd/bench --engine all --out out
go run ./cmd/bench --engine typst --json        # JSON output
go run ./cmd/bench --engine typst-raw            # static Typst doc, no injection
go run ./cmd/demo                            # writes out/resume.pdf

# Via Docker (engines pinned, fully reproducible)
# Final PDFs land in ./out (the container's /app/out is mounted to .)
go run ./cmd/bench --docker
go run ./cmd/bench --docker --out out
make bench          # local
make docker-bench  # docker

# Idiomatic Go benchmarks
go test -bench=. -benchtime=10x ./...

The benchmark writes a final PDF per engine. By default it copies them to ./out/<engine>.pdf (--out flag; set it to empty to skip). Use these to eyeball that both engines render the same resume content.

Notes

  • Tectonic fetches TeX Live packages on first compile (cached afterward); the warmup phase absorbs that cost. An offline / no-network container will fail the LaTeX run until packages are pre-cached.
  • Engine versions in the Dockerfile are pinned for reproducibility and should be bumped deliberately (and re-recorded in results/) when upgraded.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors