Use this skill when asked to benchmark pyrefly performance, run a pyrefly bench,
compare cold-start vs error-propagation latency, or update the PyTorch benchmark
pin. All benches live in pyrefly/pyrefly/benches/ and use Criterion.
Build mode matters: always build optimized, or the numbers are meaningless.
Buck uses @fbcode//mode/opt (or @fbcode//mode/opt-clang-thinlto for final
numbers). Cargo cargo bench already uses the optimized bench profile;
cargo run/cargo build would need --release. Debug builds run 3-10x slower
and are not comparable.
Never run two benchmarks in parallel — they compete for CPU and memory and the timings become unreliable.
Micro benchmarks (fast)
Deterministic, single-threaded microbenchmarks of type-checker internals. Cheap to run, good for a quick signal.
- Buck:
buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:micro_bench -- --bench - Cargo:
cargo bench --bench micro
Source: pyrefly/pyrefly/benches/micro.rs.
PyTorch benchmarks (heavy, walltime)
Three real-world benchmarks run over a pinned, multi-gigabyte PyTorch checkout
(15k+ Python files) across all cores. They are walltime benchmarks (threads,
I/O, a long cold start), not deterministic unit tests — distinct from the micro
benchmarks. Two drive the actual LSP server (cold_start, error_propagation);
one runs a cold batch check (full_check).
All three ship in one target — buck pytorch_bench, cargo bench
pytorch — and you select an individual one at runtime with a Criterion name
filter rather than picking a separate target. They live under
pyrefly/pyrefly/benches/pytorch/, one module file per bench:
pytorch/main.rs— crate root; declares the modules and callscriterion_main!aggregating the benchmarks' Criterion groups.pytorch/common.rs— shared PyTorch-checkout acquisition harness and standard LSP args.pytorch/cold_start.rs— the cold-start benchmark. Fresh server per iteration; openstorch/distributed/pipelining/_backward.pyand queries go-to-definition of theParameterimport. Proxy for time-to-first-index. Criterion idpytorch/cold_start_go_to_definition.pytorch/error_propagation.rs— the error-propagation benchmark. Warm server; editstorch/nn/__init__.pyto rebindParameterto an int and waits for the resulting type error to surface in the distant dependent_backward.py. Proxy for incremental edit-propagation latency. Criterion idpytorch/error_propagation.pytorch/full_check.rs— the full-check benchmark. FreshStateper iteration; runs exactly whatpyrefly check(project mode, no file args) does from inside the checkout — discovers the project and checks every project file across all cores. Proxy for whole-project batch throughput (not interactive latency). Criterion idpytorch/full_check.
Time cost
These are slow. A cold-start iteration is ~3-5 s on a 64-thread devvm;
error-propagation is ~2-3 s and full-check ~1-1.5 s per iteration. Criterion's
sample floor is 10, so budget roughly 2-4 minutes per benchmark. Do not run
them in CI Sandcastle by default — they are manual/heavy (the PyTorch
http_archive dep is labeled manual).
Run commands
Run all benchmarks:
# Buck (internal)
buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:pytorch_bench -- --bench
# Cargo (OSS)
cargo bench --bench pytorch
Run just one, selecting it by Criterion name filter:
# Buck (internal) — cold start
buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:pytorch_bench -- --bench cold_start
# Buck (internal) — error propagation
buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:pytorch_bench -- --bench error_propagation
# Buck (internal) — full check
buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:pytorch_bench -- --bench full_check
# Cargo (OSS)
cargo bench --bench pytorch -- cold_start
cargo bench --bench pytorch -- error_propagation
cargo bench --bench pytorch -- full_check
Useful flags (append after -- for buck; pass directly for cargo):
--list— list the benches in the binary instead of running them, e.g.buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:pytorch_bench -- --list.--quick— quicker, lower-confidence run.--noplot— skip Criterion report rendering. Required on a headless devserver with no gnuplot and no usable font: the plotters backend otherwise panics after a bench finishes withBackendError(FontError(FontUnavailable)). Timings are unaffected. Example:buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:pytorch_bench -- --bench --noplot.
PyTorch source acquisition
The PyTorch source (~550 MB unpacked, 68 MB tar.gz) is pinned by a single 40-hex
commit in pyrefly/pyrefly/benches/pytorch_pin.bzl — there is no git submodule. That
file is loaded by BUCK and parsed by the shared common module. Two providers:
- Internal (buck): fetches the pinned tarball from Manifold via
http_archiveand passes its path to the benches inPYREFLY_PYTORCH_BENCH_PATH. No github egress; Buck CAS caches it. - OSS (cargo): shallow-clones the pinned rev from github into a per-rev temp
cache on first run (needs
git+github.comegress), reused after.
Set PYREFLY_PYTORCH_BENCH_PATH to an existing checkout to bypass both. If the
checkout can't be obtained, the bench prints a skip notice and exits cleanly.
Output location
Criterion writes to target/criterion/ (HTML plots, CSV samples,
estimates.json). Because buck run / cargo bench run from the cell root, it
lands at the fbsource repo root target/criterion/. It is ephemeral — do not
commit it, and do not add repo-root ignore entries (it is already gitignored
appropriately).
TSP benchmark (fast)
benches/tsp.rs -- buck tsp_bench, cargo bench tsp, Criterion id
tsp/get_computed_type_unopened_cached. Durable regression coverage for the
TSP unopened-file solve-reuse fix (D118537886): repeats an identical
getComputedType request, on a stable snapshot, against one never-opened,
unreferenced module deliberately expensive to solve, over the plain
in-process main connection. Server spawn, fixture generation, and the first
cold request are unmeasured; each measured iteration is one logical request.
Portable, no pinned checkout, no manual label.
buck2 run @fbcode//mode/opt fbcode//pyrefly/pyrefly:tsp_bench -- --bench
cargo bench --bench tsp
Updating the PyTorch pin
Run on a machine with github access (devvm/Sandcastle have no egress):
pyrefly/pyrefly/benches/update_revision.sh <40-hex-sha>
It clones the rev, archives a tarball, rewrites pytorch_pin.bzl (rev +
sha256), and uploads the tarball to Manifold.
After a pin bump, re-check PARAM_LINE / PARAM_COL in
pytorch/cold_start.rs — they encode the position of Parameter in
_backward.py, and the cold-start bench asserts if they drift.