| name | fory-performance-optimization |
| description | Run profile-driven bottleneck optimization across Apache Fory implementations (Java, C++, Python/Cython, Go, Rust, Swift, C#, JavaScript/TypeScript, Dart, Kotlin, Scala). Use when improving serialize/deserialize throughput or latency, recovering regressions against a reference commit, diagnosing flamegraphs, fixing perf-related CI failures, or porting proven optimizations across languages without protocol or API regressions. |
Fory Performance Optimization
Mission
Deliver measurable performance improvements in Apache Fory without protocol drift, correctness regressions, benchmark-shape tricks, or accidental API rollback.
Operating Principles
- Start from data, not intuition.
- Profile before changing hot code.
- Change one bottleneck at a time.
- Benchmark sequentially on the same machine state (one benchmark process at a time).
- Compare old/new benchmark results case-by-case in adjacent pairs: run one case on
apache/main,
then immediately run that same case on the current branch before moving to the next case.
- Under high or variable host load, run multiple short adjacent baseline/current pairs. Keep each
process short and alternate sides instead of lengthening one run or batching all baseline runs.
- Keep only measured wins or explicitly requested architecture cleanups.
- Revert speculative changes that do not pay off.
- Align with reference runtimes (usually C++ first, then Rust/Java) when behavior and ownership models differ.
Enforce Hard Constraints
- Preserve wire protocol unless explicitly requested.
- Preserve cross-language semantics and xlang compatibility.
- Never run two benchmarks at the same time on one host; run exactly one benchmark command at a time.
- Do not optimize by changing benchmark payload definitions, field encodings, or benchmark methodology.
- Do not add payload-identity or repeated-input caches that depend on benchmark shape.
- Do not restore removed APIs/legacy wrappers when the user forbids it.
- Do not preserve legacy/dead code or stale docs in optimization rounds; remove them when touched.
- Keep API surface minimal: do not add new API unless required by protocol/correctness or explicitly requested.
- Never add public hacky API for performance shortcuts; keep optimization helpers internal/private and conceptually clean.
- Do not hide regressions behind unsafe compiler flags or benchmark-only code paths.
- Keep optimization surfaces nested-safe; avoid root-only shortcuts unless they are architecturally valid and requested.
- Do not add reader-side validation solely to produce an earlier or more precise malformed-input
error. A necessary crash, panic, undefined-behavior, out-of-bounds, resource-amplification,
no-progress, state-pollution, type, or policy guard must keep its hot success path to a primitive
branch and move exception allocation and message formatting into a cold no-inline helper when
supported. If an existing bounds-safe downstream operation already raises a controlled root
error, do not duplicate its validation on the hot path.
Execute Workflow
- Read context and constraints.
- Read
tasks/perf_optimization_rounds.md and tasks/lessons.md.
- Read the relevant spec in
docs/specification/ for any path that may affect wire behavior.
- Record explicit user constraints (forbidden APIs, naming, architecture, protocol rules).
- Define target and baseline.
- Identify one primary KPI (for example
Struct Serialize ns/op or ops/sec).
- Benchmark current
HEAD.
- If a reference commit is provided, persist its built benchmark artifact and commit identity. Treat
stored numbers as historical context, not a substitute for an adjacent baseline run in each
comparison pair.
- Profile the hotspot.
- Capture a flamegraph or sampled stacks on the exact benchmark command.
- Quantify top costs by bucket (runtime bookkeeping, dispatch, allocation/copy, map/cache operations, buffer growth, metadata parse/validation).
- Tie each bucket to concrete file/line ownership before proposing changes.
- Form one round hypothesis.
- State one bottleneck and one expected effect.
- Prefer structural fixes over micro-tweaks.
- If another runtime already solved the same bottleneck, port its design shape first.
- Implement minimal change.
- Touch the smallest surface that can validate the hypothesis.
- Keep invariants explicit: protocol bytes, ownership, cache lifetime, reference semantics, nullability, schema-compatible behavior.
- Verify correctness.
- Run language-local build/test/lint for the touched implementation.
- Run cross-language checks when runtime/type/protocol behavior can affect xlang.
- Confirm serialized sizes and compatibility expectations where applicable.
- Benchmark and compare.
- Run targeted benchmark at least twice sequentially.
- Pair each baseline case with the matching current-branch case before starting another case, so
both measurements see closer machine load conditions.
- When host load is high or pair results conflict, use several short baseline/current pairs with the
same warmup and measurement settings. Run
baseline, current, baseline, current as separate
processes; never run all baseline samples before all current samples.
- Record every pair while it runs. Exclude a pair only with objective contamination evidence such as
a competing process, load spike, interruption, or throughput collapse; record the exclusion and
do not cherry-pick by direction.
- Compare paired deltas using their median and dispersion. Do not optimize from a single pair,
non-adjacent samples, or a contaminated result. If the retained pairs do not establish a stable
signal, stop and wait for a cleaner window instead of changing code against the apparent result.
- Run one short full-suite sanity benchmark to catch collateral regressions.
- Decide keep or revert.
- Keep only if gain is repeatable or cleanup is explicitly requested and accepted with measured tradeoff.
- Revert if performance regresses or gain is within noise and complexity increases.
- If a required cleanup regresses, redesign inside the new architecture instead of restoring banned patterns.
- Log every round.
- Append one round entry to
tasks/perf_optimization_rounds.md before starting the next round.
- Include hypothesis, code change, exact commands, before/after numbers, and keep/revert decision.
- Commit retained non-trivial rounds immediately.
- Re-plan on instability.
- Stop and re-plan when benchmark runs conflict, machine contention is suspected, or profile does not match hypothesis.
- On a busy machine, re-plan the measurement schedule to multiple short adjacent pairs before
forming an optimization hypothesis from benchmark deltas.
- Re-ground on current
HEAD after reset/rebase/checkout events before making further changes.
Apply Decision Rules
- Treat <1-2% movement as noise unless repeated under controlled runs.
- Require explicit proof for complexity-increasing optimizations.
- Prefer deleting dead APIs and dead state quickly after refactors.
- Keep naming/API cleanup only if performance remains in band.
- Never run before/after comparisons in parallel.
Use References
Produce Output
When finishing an optimization task, report:
- Baseline command and numbers.
- Final command and numbers.
- Net delta on primary KPI.
- Correctness and compatibility verification run.
- Kept vs reverted rounds and rationale.