Visitar URL original
perf(lib): shrink the help renderer further by outlining wrapping, slicing and searching by jdx · Pull Request #1553 · jdx/usage · GitHub
Skip to content

perf(lib): shrink the help renderer further by outlining wrapping, slicing and searching - #1553

Merged
jdx merged 67 commits into
mainfrom
perf/argv-help-size
Oct 14, 2026
Merged

jdx merged 67 commits into
mainfrom
perf/argv-help-size

Conversation

@jdx

@jdx jdx commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

https://entire.io/gh/jdx/usage/trails/94

Second pass on the size of usage-argv's help rendering, which is still the largest piece of what usage adds to a CLI binary. Stacked on #1551 (perf/argv-size); merge that first, then retarget.

What changed

  • The text wrapper was inlined into page_sections at every call. Keeping write_wrapped_indented and wrap_lines out of line is the single largest saving, about 21 KB.
  • About 60 &str[a..b] sites in help.rs and help/template.rs each carried a bounds check and a slice-error call. They go through three small non-inlined helpers (head, tail, slice), about 5 KB.
  • own_and_global builds flag spellings with plain helpers (push_forms, has_form, first_long, ...) instead of iterator chains, and the char and string searches use one byte scan rather than core's searcher and its monomorphised copies.
  • Line and ;/+ splitting use one split_line/split_first; style names are one packed string; the prose span table is a match (no relocations); and several small panic sites are gone.

Help output is byte-identical (plain, forced colour, and at width 200 on oxfmt's real CLI) and the full workspace test suite passes.

Measured

Linux x86_64, oxlint and oxfmt built from the oxc PR with usage-argv patched in from this branch, exact loadable-section sums against the parent branch: oxlint 14,906,504 → 14,863,744 B (-42,760), oxfmt 9,243,420 → 9,200,652 B (-42,768).

Speed: interleaved min-of-500 --help is 769 µs against 780 µs for the parent (parity; two copies of the same binary differ by about 35 µs here). Instruction count for oxlint --help under cachegrind is +0.7% (1,637,947 → 1,648,957), the price of the byte scan's first-byte filter. Parse paths are untouched.

Ideas that need public API changes (for 8.0) are in #1550.

This description was generated by Claude Code.

🤖 Generated with Claude Code

AI-assisted — Tool: Claude Code; model: anthropic/claude-sonnet-5-5; version: unavailable.


Note

Low Risk
Cold-path help rendering only, with stated byte-identical output; parse and argv handling are unchanged aside from push_forms in spec layout.

Overview
Shrinks the usage-argv help renderer binary footprint by routing repeated string work through shared, #[inline(never)] helpers (head/tail/slice, find_str/find_byte, lines_of, split_line, a Text trait for trim/blank checks) instead of duplicating core slicing and search at dozens of call sites in help.rs and help/template.rs.

Template and inline styling drop static string tables for span kinds and style names (packed span_kind match + STYLE_NAMES with style_index), and flag spelling collection uses push_forms / small lookup helpers rather than iterator chains.

page() now resolves path/chain once then calls assembled_help / recursive_help for both routed and address-based lookup; a few panic paths become graceful Option handling (empty chain, implicit positional clause).

Reviewed by Cursor Bugbot for commit ace6eb0. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes
    • Help output now handles empty command chains and missing or unavailable items without failing.
    • Implicit clauses omit the argument synopsis when no visible argument is available.

jdx and others added 30 commits October 14, 2026 16:51
Help rows, usage lines and annotations were assembled with write! and format!,
which pulls core::fmt argument handling into every call site. Plain pushes
through small out-of-line helpers produce the same bytes and a smaller binary.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
own_and_global built a String for every long and short spelling just to test
membership. A small Form enum compares them in place. Also drives the ANSI style
lookup from the published STYLES table.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
wrap returned a Vec<String> that every caller walked once. One wrapping routine
now hands each line to the writer, so the three callers share it and none of
them allocates the intermediate lines.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The sections-only fallback duplicated the template scanner and the blank-run
collapse. One loop now serves both.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The inline emphasis and template token tables were rebuilt on the stack for
every character; statics keep them out of the code.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n notes

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… new spec

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A page is written in hundreds of small pushes and every inlined push_str or push
carries its own capacity check and copy. Routing help text through one
out-of-line method each for strings and characters shrinks the binary; a help
page is far too cold for the call to show.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…site formatting

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… chain

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r chains

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
jdx and others added 17 commits October 14, 2026 17:24
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4XRV5KEFZA9JN2VAGZE06JA
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4XSX7GJ8VGHS4MEP73EQ7WS
@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Central YAML (base), Organization UI (inherited)
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: bee9b571-a004-4f94-becb-ee75ee1c8cfc
📥 Commits

Reviewing files that changed from the base of the PR and between 2cb3068 and ace6eb0.

📒 Files selected for processing: 3
  • argv/src/help.rs
  • argv/src/help/template.rs
  • argv/src/spec.rs

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.

📝 Walkthrough

Shared text helpers now support help-template processing, help rendering, and command-page assembly. Flag-form collection and selection also use these helpers. Several help-generation paths now handle missing command metadata or unavailable items without panicking.

Changes: Help text and page assembly

Layer / File(s) Summary
Shared text helpers
argv/src/help.rs
Private helpers provide text trimming, searching, splitting, joining, line collection, and byte access.
Template parsing and rendering
argv/src/help.rs, argv/src/help/template.rs
Template style lookup, validation, substitution, blank-line handling, marked rendering, and ANSI processing use shared helpers. A test checks that style names match the published style order.
Help text formatting
argv/src/help.rs
Logo, ANSI, inline styling, line processing, and wrapping use shared helpers while retaining existing delimiter and layout rules.
Flag forms and command help assembly
argv/src/help.rs, argv/src/spec.rs
Flag forms use shared collection and selection helpers. Command-page assembly uses updated lookups, and some missing items are skipped instead of indexed directly.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Refactor

Suggested reviewers: lu-zero

Merge Risk: ⚪ Minimal · up to ace6e

No actionable issue remains from this review; the change is mergeable after normal checks.

Pre-merge checks: Passed 5
Passed Passed checks: 5 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the performance-focused change to the help renderer and names the main optimization areas: wrapping, slicing, and searching.
Docstring Coverage Passed Docstring coverage is 85.11% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 94 functions across 3 files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

This PR moves repeated help-rendering work into shared helpers to reduce binary size.

  • Help pages keep the same text with less repeated rendering code.
  • Help templates keep the same styles with less lookup data.
  • Command help keeps inherited flag spellings distinct with shared lookup code.

The full PR diff was reviewed because the previous and current heads have different merge bases with the target commit.

Reviews (2) · Last reviewed commit: "fix(lib): drop the SPANS table that span..." · Reviewed by Greptile

Base automatically changed from perf/argv-size to main October 14, 2026 18:32
jdx and others added 2 commits October 14, 2026 18:35
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…main

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Entire-Checkpoint: 01M4XV7YS9DRQJ4TX2SARENB30
@jdx
jdx added this pull request to stack #1556 October 14, 2026 18:39
@jdx
jdx merged commit 53dfb4b into main Oct 14, 2026
14 checks passed
@jdx
jdx deleted the perf/argv-help-size branch October 14, 2026 19:31
jdx pushed a commit that referenced this pull request Oct 14, 2026
<!-- entire-trail-link-start -->
https://entire.io/gh/jdx/usage/trails/93
<!-- entire-trail-link-end -->

### 📚 Documentation

- replace V7.md with V8.md for pending breaking changes by
[@jdx](https://github.com/jdx) in
[#1550](#1550)

### ⚡ Performance

- **(complete)** shrink completion splitting and error suggestions by
[@jdx](https://github.com/jdx) in
[#1554](#1554)
- **(lib)** shrink the help, completion and error rendering code in
usage-argv by [@jdx](https://github.com/jdx) in
[#1551](#1551)
- **(lib)** shrink the help renderer further by outlining wrapping,
slicing and searching by [@jdx](https://github.com/jdx) in
[#1553](#1553)

### 📦️ Dependency Updates

- bump jactionlint to 2.2.0 and mbx to 1.24.0 by
[@jdx](https://github.com/jdx) in
[#1549](#1549)
@github-actions

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
markdown ██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ 266,385,976 → 266,405,863 +0.01% 47.19 → 46.91ms -0.59%
startup ███████████████▂▁ 1,017,453 → 1,012,502 -0.49% 1.46 → 1.52ms +3.63%

No instruction-count regression above 1%.

Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run.

Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes.

Shadow comparison

Parsing mise use -g node@20 against a shadow of mise's committed spec.
Reported, not gated: the shadow grows as the derive learns to express more, so
what to watch is the ratio rather than either column.

framework stripped binary, bytes
usage 1250640
bpaf 2494640
clap 3100904
framework instructions, cold parse vs usage
usage 8293 —
clap 6313736 761x
bpaf 21909300 2641x
                                              min       p01       p10    median
usage-rs: argv -> struct                      767       814       819       829  ns
clap: build tree + parse -> struct        1239259   1245395   1252859   1269457  ns
bpaf: build parser + parse -> struct      3410870   3410870   3454831   3495890  ns

usage: argv -> struct                             840 ns      0.84 µs
clap: build tree + parse -> struct            1247968 ns   1247.97 µs
clap: parse -> struct, tree reused              49258 ns     49.26 µs
clap: build tree only                          745661 ns    745.66 µs

ace6eb0fb4df vs 2cb3068baab4 · measured on the runner, not pushed to the history.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant