aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-08-20 03:49:21 +0800
committer魏曹先生 <1992414357@qq.com>2026-08-20 03:49:21 +0800
commit65642c1dad1138f4c72536b86123ffeefd33ba60 (patch)
treed0a79ad8dd57a0d3ddf90edbd8ea018db81dfa74
parent0ee5d865a1eaf8b409c2b310ab16ee5f42a0f14a (diff)
feat(dispatch): add auto dispatch strategy selection
Introduce automatic dispatch strategy selection with three mutually-exclusive features: `dispatch_linear`, `dispatch_tree`, and `dispatch_phf`. When none are enabled, the generator picks the optimal strategy at compile time based on command table shape. Add CHD minimal perfect hash generator, refactor trie fallback into a shared method to keep generated code linear, and add a benchmark harness to validate strategy selection.
-rw-r--r--CHANGELOG.md30
-rw-r--r--docs/dev/_sidebar.md10
-rw-r--r--docs/dev/pages/issues/_modify-dispatcher-syntax.md2
-rw-r--r--docs/dev/pages/issues/_remove-pack-macros.md2
-rw-r--r--docs/dev/pages/issues/_remove-parser-feature.md2
-rw-r--r--docs/dev/pages/issues/_remove-with-dispatcher.md2
-rw-r--r--docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md76
-rw-r--r--docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md57
8 files changed, 114 insertions, 67 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed8d388..f87fbb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,7 +62,35 @@ None
#### Optimizations:
-None
+1. **[`macros:dispatch`]** Introduced an "auto" dispatch-strategy selection mode plus a third explicit strategy. Previously, `dispatch_tree` was the only strategy-related feature (enabling a char-level trie; with it disabled, a linear longest-prefix list was used). Now there are three mutually-exclusive dispatch features — `dispatch_linear`, `dispatch_tree`, `dispatch_phf` — and when none is enabled, `gen_program!` selects the best strategy from the command table automatically.
+
+ **New feature flags** (`mingling/Cargo.toml`, `mingling_macros/Cargo.toml`):
+
+ - `dispatch_linear` — forces the linear longest-prefix list generator
+ - `dispatch_tree` — forces the char-level trie generator (unchanged behavior)
+ - `dispatch_phf` — new: forces the CHD minimal perfect-hash generator
+ - `bench_support` — workspace-internal feature (in `mingling_macros`) that compiles all three generators and exposes the `bench_cell!` proc macro used by the `dev/bench/dispatch` harness
+
+ The three dispatch features are mutually exclusive: enabling more than one triggers a `compile_error!` (in `mingling_macros/src/lib.rs`). Enabling none selects _auto_ mode.
+
+ **Auto mode** (`mingling_macros/src/systems/dispatch_auto.rs`): when no dispatch feature is enabled, `program_final_gen` calls `dispatch_auto::select_strategy(&entries)` at macro-expansion time, which picks `Linear`, `Trie`, or `Phf` from the normalized command table based on a cost model calibrated against the `dev/bench/dispatch` matrix:
+
+ - deep nested chains at modest sizes (`max_words ≥ 8`, `n ≤ 128`) → linear list (a few short memcmps beat the trie's per-level `nth(0)` walk plus the fallback call on non-leaf hits);
+ - single-word tables with long names → perfect hash (one hash beats the trie's char walk once names grow past ~16 chars);
+ - small tables (`n ≤ 64`) → linear vs trie by a cost model (linear wins on short names, loses once `count × length` grows);
+ - everything else → char trie (O(depth) hit cost independent of table size, best miss path).
+
+ **New perfect-hash generator** (`mingling_macros/src/systems/dispatch_phf_gen.rs`): implements a CHD (Belazzougui, Botelho, Dietzfelbinger) minimal perfect hash computed at macro-expansion time. Semantics match the other two generators exactly: longest registered word-aligned prefix wins; every hash hit is verified with an exact byte equality against the stored key; duplicate normalized names are dropped (first wins). Runtime cost is one byte scan over the first `max_words` words plus at most `max_words` double-hash + verify attempts; code size is O(1) in the command count.
+
+ **Trie generator refactor** (`mingling_macros/src/systems/dispatch_tree_gen.rs`): the trie's longest-prefix fallback is no longer inlined into every arm. Each trie node gets an id and every arm _calls_ a single generic `__trie_fallback<G>` method that runs that node's exact-endpoint checks and tail-recurses to the parent, returning `None` when nothing in the chain matches. This keeps the generated code linear in the table size — previously, inlining the whole fallback chain per arm grew quadratically with nesting depth (a 1024×16 nested table emitted ~13 MB of tokens). The generator now returns two token streams: the `dispatch_args` method (for the `ProgramCollect` trait impl) and the `__trie_fallback` method (for an inherent impl of the program type). The inherent impl is emitted inside the generated program's `impl` block (via the new `dispatch_extra` handling in `program_final_gen.rs`).
+
+ **New feature constants** in `mingling/src/features.rs`: `MINGLING_DISPATCH_LINEAR` and `MINGLING_DISPATCH_PHF` (both `false`/`true` gated on their features, alongside the existing `MINGLING_DISPATCH_TREE`).
+
+ **New benchmark harness** (`dev/bench/dispatch/`): a workspace-internal `mingling_bench` crate that measures the three explicit strategies plus auto mode across a matrix of command-table shapes (length 4/8/16/32 × count 128/256 × single-word/multi-word/nested-depth-4/nested-depth-10). A `build.rs` generates the full cell matrix via the new `bench_cell!` proc macro, and `src/main.rs` renders a `prettytable` report with per-cell hit/miss ns/op, geomeans, per-cell strategy wins, and auto-selection quality (exact-match and within-5% counts). A `cargo dispatch-bench` alias was added to `.cargo/config.toml`.
+
+ **Doc updates**: `ProgramCollect::dispatch_args` docs and `register_dispatcher!` docs updated to describe the three strategies and auto mode.
+
+ _No behavioral change for existing code that uses `dispatch_tree` (still the trie) or no dispatch feature (now auto-selected instead of always linear — the auto rules preserve the old linear behavior for the previously-common small/nested command tables). The mutual-exclusion `compile_error!` only fires when multiple dispatch features are enabled, which was previously impossible and remains an error.
#### Features:
diff --git a/docs/dev/_sidebar.md b/docs/dev/_sidebar.md
index e0a77d3..9ed13d1 100644
--- a/docs/dev/_sidebar.md
+++ b/docs/dev/_sidebar.md
@@ -1,18 +1,18 @@
- [Welcome!](README)
* ❓ Issues
* [[Solved] The Picker2 Arguments Parser](pages/issues/_add-picker2)
- * [[T1] Modify the dispatcher! Syntax](pages/issues/_modify-dispatcher-syntax)
- * [[T0] Remove the pack! Family of Macros](pages/issues/_remove-pack-macros)
- * [[T0] Remove the parser Feature](pages/issues/_remove-parser-feature)
+ * [[Solved] [T1] Modify the dispatcher! Syntax](pages/issues/_modify-dispatcher-syntax)
+ * [[Solved] [T0] Remove the pack! Family of Macros](pages/issues/_remove-pack-macros)
+ * [[Solved] [T0] Remove the parser Feature](pages/issues/_remove-parser-feature)
* [[Solved] Remove r_print! and r_println! Macros](pages/issues/_remove-r-print-macro)
- * [[T0] Remove with_dispatcher and with_dispatchers](pages/issues/_remove-with-dispatcher)
+ * [[Solved] [T0] Remove with_dispatcher and with_dispatchers](pages/issues/_remove-with-dispatcher)
+ * [[Solved] [T2] Automated dispatcher_tree Optimization Decisions](pages/issues/_t2_automated-dispatch-tree-optimization)
* [[Solved] The Command Macro](pages/issues/_the-command-macro)
* [[Solved] The Mod Pathfinder](pages/issues/_the-mod-pathfinder)
* [[T0] Generalize the REPL System](pages/issues/t0_generalize-repl-system)
* [[T1] Higher-Level Abstractions for the Completion System](pages/issues/t1_completion-higher-level-abstractions)
* [[T1] Move structural_renderer from mingling_core to mingling](pages/issues/t1_move-structural-renderer)
* [[T1] The pathf_export Attribute Macro](pages/issues/t1_pathf-export-macro)
- * [[T2] Automated dispatcher_tree Optimization Decisions](pages/issues/t2_automated-dispatch-tree-optimization)
* [The Next-Gen Mingling Pipeline](pages/issues/the-next-pipeline)
* [Some Situations Where You'd Be Like "Shit!"](pages/issues/the-shit-time)
* 💡 Abouts
diff --git a/docs/dev/pages/issues/_modify-dispatcher-syntax.md b/docs/dev/pages/issues/_modify-dispatcher-syntax.md
index ed409ef..cfee64b 100644
--- a/docs/dev/pages/issues/_modify-dispatcher-syntax.md
+++ b/docs/dev/pages/issues/_modify-dispatcher-syntax.md
@@ -1,4 +1,4 @@
-<h1 align="center">[T1] Modify the dispatcher! Syntax</h1>
+<h1 align="center">[Solved] [T1] Modify the dispatcher! Syntax</h1>
<p align="center">
Breaking: drop the <code>CMD*</code> struct from the explicit form of <code>dispatcher!</code>
</p>
diff --git a/docs/dev/pages/issues/_remove-pack-macros.md b/docs/dev/pages/issues/_remove-pack-macros.md
index 74c21aa..153004e 100644
--- a/docs/dev/pages/issues/_remove-pack-macros.md
+++ b/docs/dev/pages/issues/_remove-pack-macros.md
@@ -1,4 +1,4 @@
-`<h1 align="center">[T0] Remove the pack! Family of Macros</h1>
+`<h1 align="center">[Solved] [T0] Remove the pack! Family of Macros</h1>
<p align="center">
Breaking: retire the entire <code>pack!</code> family in favor of the <code>Grouped</code> derive
</p>
diff --git a/docs/dev/pages/issues/_remove-parser-feature.md b/docs/dev/pages/issues/_remove-parser-feature.md
index c2a11c0..7653c47 100644
--- a/docs/dev/pages/issues/_remove-parser-feature.md
+++ b/docs/dev/pages/issues/_remove-parser-feature.md
@@ -1,4 +1,4 @@
-<h1 align="center">[T0] Remove the parser Feature</h1>
+<h1 align="center">[Solved] [T0] Remove the parser Feature</h1>
<p align="center">
Breaking: retire the legacy argument parsing in favor of <code>picker</code>
</p>
diff --git a/docs/dev/pages/issues/_remove-with-dispatcher.md b/docs/dev/pages/issues/_remove-with-dispatcher.md
index b0d972f..42b12e5 100644
--- a/docs/dev/pages/issues/_remove-with-dispatcher.md
+++ b/docs/dev/pages/issues/_remove-with-dispatcher.md
@@ -1,4 +1,4 @@
-<h1 align="center">[T0] Remove with_dispatcher and with_dispatchers</h1>
+<h1 align="center">[Solved] [T0] Remove with_dispatcher and with_dispatchers</h1>
<p align="center">
Breaking: make <code>Dispatcher</code> registration compile-time collected in all modes
</p>
diff --git a/docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md b/docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md
new file mode 100644
index 0000000..d2e2473
--- /dev/null
+++ b/docs/dev/pages/issues/_t2_automated-dispatch-tree-optimization.md
@@ -0,0 +1,76 @@
+<h1 align="center">[Solved] [T2] Automated dispatcher_tree Optimization Decisions</h1>
+<p align="center">
+ Feature: let Mingling decide when <code>dispatch_tree</code> pays off (implemented)
+</p>
+
+> [!NOTE]
+>
+> This item is **implemented**. It depends on [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher).
+
+## Background
+
+`dispatch_tree` provides a faster dispatch path, but it is not always a win. Currently users must manually enable the `dispatch_tree` feature and make the trade-off themselves.
+
+After dispatcher registration becomes compile-time collected (see [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher)), Mingling can know the full set and depth of registered commands at compile time — making automated decisions implementable.
+
+## Plan
+
+Mingling can automatically decide whether to use `dispatcher_tree` to optimize dispatch efficiency based on the current number and depth of registered commands, so users no longer need to manually enable the `dispatch_tree` feature.
+
+### Conditions
+
+`dispatch_tree` has an advantage in cases where command depth is too high and the number of commands is too large. However, if the number of commands is too small, the increased CPU prediction failure rate will inevitably make it less efficient than linear lookup; specifics need to be tuned during implementation.
+
+### Resolve the `pathf` + `dispatch_tree` build-dependency issue
+
+Additionally, the issue where `pathf` + `dispatch_tree` must be explicitly specified in `[build-dependencies]` will be resolved:
+
+```toml
+# Before
+[build-dependencies.mingling]
+version = "0.4.0"
+features = [ "build", "pathf", "dispatch_tree" ] # `dispatch_tree` must be explicitly specified for `pathf` to recognize it
+
+# After
+[build-dependencies.mingling]
+version = "0.4.0"
+features = [ "build", "pathf" ] # No `dispatch_tree` feature; `pathf` no longer needs to consider its branches
+```
+
+## Final Implementation
+
+The automated dispatch-strategy selection is now in place. A new `dispatch_auto` module (the default when no dispatch feature is enabled) picks at macro-expansion time from three strategies — **linear list**, **char trie**, and **perfect hash** — based on a cost model calibrated against the `dev/bench/dispatch` benchmark matrix.
+
+### Two new dispatch features
+
+In addition to the existing `dispatch_tree`, two new mutually-exclusive features now exist:
+
+- **`dispatch_linear`** — force linear longest-prefix list (the former default).
+- **`dispatch_phf`** — force a CHD minimal perfect hash (constant-time lookup, O(1) code size).
+- **`dispatch_tree`** — force the char-level trie.
+- **(none)** — **auto mode**: pick the best strategy from the command table.
+
+Enabling more than one triggers a `compile_error!`.
+
+### Auto-selection heuristic
+
+`dispatch_auto::select_strategy` inspects the normalized command table (names, depth, nesting) and picks:
+
+- **deep nested chains at modest sizes** (`max_words ≥ 8`, `n ≤ 128`) → linear list (short memcmps beat the trie's per-level char walk plus fallback calls);
+- **single-word tables with long names** (avg_len ≥ 16–24) → perfect hash (one hash beats the char walk);
+- **small tables** (`n ≤ 64`) → linear vs trie by an internal cost model;
+- **everything else** → char trie (O(depth) hits, linear code size after the fallback-chain refactor).
+
+The heuristic is empirical and may drift as the benchmark matrix grows.
+
+### Benchmark harness
+
+A workspace-internal harness `dev/bench/dispatch` (`cargo dispatch-bench`) measures all four strategies across a `len×count×type` matrix (4/8/16/32 × 128/256 × single/multi/nested4/nested10), reporting per-cell ns/op for hits and misses, geometric means, and how often auto matches the per-cell best / stays within 5%.
+
+### Trie code-size fix
+
+The trie generator was rewritten so the longest-prefix fallback is a single shared `__trie_fallback` method (called, not inlined, per arm) rather than inlined into every arm. This keeps generated code linear in the table size — a 1024×16 nested table previously emitted ~13 MB of tokens.
+
+<p align="center" style="font-size: 0.85em; color: gray;">
+ Written by @Weicao-CatilGrass
+</p>
diff --git a/docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md b/docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md
deleted file mode 100644
index 691fd1a..0000000
--- a/docs/dev/pages/issues/t2_automated-dispatch-tree-optimization.md
+++ /dev/null
@@ -1,57 +0,0 @@
-<h1 align="center">[T2] Automated dispatcher_tree Optimization Decisions</h1>
-<p align="center">
- Feature: let Mingling decide when <code>dispatch_tree</code> pays off (under consideration)
-</p>
-
-> [!NOTE]
->
-> This item is **under consideration**. It depends on [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher).
-
-## Background
-
-`dispatch_tree` provides a faster dispatch path, but it is not always a win. Currently users must manually enable the `dispatch_tree` feature and make the trade-off themselves.
-
-After dispatcher registration becomes compile-time collected (see [Remove with_dispatcher and with_dispatchers](t0_remove-with-dispatcher)), Mingling can know the full set and depth of registered commands at compile time — making automated decisions implementable.
-
-## Plan
-
-Mingling can automatically decide whether to use `dispatcher_tree` to optimize dispatch efficiency based on the current number and depth of registered commands, so users no longer need to manually enable the `dispatch_tree` feature.
-
-### Conditions
-
-`dispatch_tree` has an advantage in cases where command depth is too high and the number of commands is too large. However, if the number of commands is too small, the increased CPU prediction failure rate will inevitably make it less efficient than linear lookup; specifics need to be tuned during implementation.
-
-### Resolve the `pathf` + `dispatch_tree` build-dependency issue
-
-Additionally, the issue where `pathf` + `dispatch_tree` must be explicitly specified in `[build-dependencies]` will be resolved:
-
-```toml
-# Before
-[build-dependencies.mingling]
-version = "0.4.0"
-features = [ "build", "pathf", "dispatch_tree" ] # `dispatch_tree` must be explicitly specified for `pathf` to recognize it
-
-# After
-[build-dependencies.mingling]
-version = "0.4.0"
-features = [ "build", "pathf" ] # No `dispatch_tree` feature; `pathf` no longer needs to consider its branches
-```
-
-## Tasks
-
-- [ ] Collect statistics about registered commands (count, depth) at compile time
-- [ ] Benchmark / tune the threshold between linear lookup and `dispatch_tree`
-- [ ] Implement the automatic decision and wire it into dispatch code generation
-- [ ] Remove the manual `dispatch_tree` feature toggle (or keep it as an override?)
-- [ ] Refactor `pathf` so it no longer branches on `dispatch_tree`
-- [ ] Update examples, tests, and docs
-
-## 🕘 Progress
-
-- [ ] Under Consideration
-- [ ] In Progress
-- [ ] Complete
-
-<p align="center" style="font-size: 0.85em; color: gray;">
- Written by @Weicao-CatilGrass
-</p>