Skip to content

Performance Optimization. Non-cropped Status-Map. Dependency Cleanup. Path-Trie + Cached executed.#12

Open
SerhiiRI wants to merge 2 commits into
feat/console-debug-and-update-documentationfrom
feat/performance-optimization
Open

Performance Optimization. Non-cropped Status-Map. Dependency Cleanup. Path-Trie + Cached executed.#12
SerhiiRI wants to merge 2 commits into
feat/console-debug-and-update-documentationfrom
feat/performance-optimization

Conversation

@SerhiiRI
Copy link
Copy Markdown
Contributor

Breaking Changes

BREAKING execute accepts an optional third argument — a config map that replaces the old binding-based *execute-config* dynamic var for passing options. Users no longer need (binding [utils/*execute-config* {...}] (execute ...)).

  • (execute registry instruction) — unchanged
  • (execute registry instruction {:error-data-string false}) — new opts map
  • Config keys: :error-data-string, :hook-execute-start, :hook-execute-end
  • Config is inherited by nested execute calls; inner calls can override specific keys.

BREAKING execute-trace signature changed from (execute-trace exec-fn) to (execute-trace registry instruction) / (execute-trace registry instruction opts). No longer requires wrapping in a zero-arg function.

BREAKING Removed :debug-result configuration option. Internal structures (:internal/cm-list, :internal/cm-dependency, :internal/cm-running-order, :internal/path-trie, :internal/cm-results, :internal/original-instruction, :registry) are now always retained in the status-map. If you relied on stripped status-maps, dissoc internal keys yourself.

BREAKING Removed dependency modes :all-inside-recur and :point-and-all-inside-recur. Supported modes: :point, :all-inside, :none.

BREAKING Removed print-stats, print-trace (print-deep-stats) from commando.impl.utils. Replaced by commando.debug namespace.

BREAKING registry_test.clj renamed to registry_test.cljc (cross-platform).

BREAKING status-map return result as is

Added

ADDED commando.debug namespace — dedicated module for debug visualization:

  • execute-debug — execute and visualize in one of six display modes: :tree, :table, :graph, :stats, :instr-before / :instr-after. Supports combining multiple modes via vector.
  • execute-trace — trace all nested commando/execute calls with timing and structure.

ADDED commando.impl.pathtrie module — trie data structure for O(depth) command lookup by path. Built during the same traversal pass as command discovery, eliminating extra passes over the instruction tree.

ADDED new status-map keys always present after execution:

  • :internal/cm-results — map {CommandMapPath -> resolved-value} with result of each command's :apply function.
  • :internal/path-trie — nested trie for efficient command lookup by path.
  • :internal/original-instruction — the original instruction before command evaluation.

ADDED structural-command-type? and structural-command-types in commando.impl.registry for detecting internal structural commands (:instruction/_value, :instruction/_map, :instruction/_vec).

Performance

OPTIMIZED find-commands BFS traversal in commando.impl.finding_commands:

  • Replaced vector-based queue with transient index-based queue (O(N) vs O(N^2) from subvec+into).
  • Transient set for found-commands accumulation.
  • Direct enqueue-coll-children! / enqueue-command-children! instead of intermediate mapv vectors.
  • Path-trie built in the same pass — no separate traversal needed.

OPTIMIZED execute-commands in commando.impl.executing:

  • Transient results map avoids N persistent map copies during execution loop.
  • Index-based loop with nth instead of rest/first on remaining commands.

OPTIMIZED build-dependency-graph in commando.impl.dependency:

  • Accepts pre-built path-trie from find-commands instead of rebuilding it.
  • Transient accumulation for forward dependency map.
  • :all-inside dependency resolution uses reduce-kv on trie subtree instead of dissoc+vals+keep+set chain.

OPTIMIZED topological-sort in commando.impl.graph:

  • Transient maps during in-degree computation.
  • Transient queue for sorted result accumulation.

OPTIMIZED CommandMapPath in commando.impl.command_map:

  • Hash computed once at construction time and cached.
  • vector-starts-with? uses indexed loop instead of lazy seq/take.

OPTIMIZED Malli validation in commando.commands.builtin:

  • Pre-computed validators and explainers for each command spec.
  • Cached coercer for status-map messages. Avoids re-creating schemas on every call.

Fixed

FIXED execute-command-impl in commando.impl.executing — guard for non-map command-data before calling dissoc on driver keys (:=>, "=>).

FIXED point dependency errors in commando.impl.dependency now include :command-path, :path, and :command in error data.

Updated

UPDATED resolve-relative-path in commando.impl.dependency — refactored from reduce to recursive loop for clarity and correct early termination.

UPDATED find-anchor-path in commando.impl.dependency — refactored from reduce to loop.

UPDATED documentation — restructured README.md with comprehensive status-map documentation, improved navigation, "Managing the Registry" and "Debugging" sections. Moved doc files to examples/ with runnable code examples.

UPDATED performance test alias from :performance to :performance-core in deps.edn.

UPDATED tests — split monolithic core_test.cljc into focused namespaces: dependency_test.cljc, finding_commands_test.cljc, graph_test.cljc, pathtrie_test.cljc. Added debug_test.cljc. Converted registry_test to .cljc for cross-platform support.

…zations, execute config map, updated docs and tests

BREAKING execute 3rd arg is now config map instead of binding. Removed :all-inside-recur and
:point-and-all-inside-recur dependency modes. Removed
print-stats/print-trace from utils. Changed execute-trace signature.
ADDED commando.debug namespace — execute-debug with 6 display modes,
execute-trace for nested call tracing.
ADDED commando.impl.pathtrie — trie for O(depth) command lookup, built
during find-commands pass.
ADDED status-map keys: :internal/cm-results, :internal/path-trie,
:internal/original-instruction.
OPTIMIZED find-commands (transient queue + set),
execute-commands (transient results, index loop),
build-dependency-graph (trie-based, transients),
topological-sort (transient maps/queue),
CommandMapPath (cached hash), Malli validation (pre-computed
validators).
FIXED execute-command-impl guard for non-map command-data. Fixed point
dependency error data.
UPDATED resolve-relative-path and find-anchor-path refactored to
loop. Updated README, docs, tests split and .cljc conversion.
@SerhiiRI SerhiiRI self-assigned this Mar 28, 2026
@SerhiiRI SerhiiRI requested a review from Kaspazza March 28, 2026 07:08
@SerhiiRI SerhiiRI changed the title Console debug namespace, pathtrie module, transient-based perf optimi… Performance Optimization. Non-cropped Status-Map. Dependency Cleanup. Path-Trie + Cached executed. Mar 28, 2026
…ning about replacemnt for deftype constructor ->CommandMapPath
Copy link
Copy Markdown
Contributor

@Kaspazza Kaspazza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally really awesome change! A lot of good stuff!

I was thinking about the trade off, between code readability and performance optimization.
Because it gets harder to read it, but for our library performance > readability.
But we can do as much as we can to ensure that this code is written there is for optimization, so next time there is change there it is important to test how it impacts performance and so that it does not get lost.

So it would be beneficial to add in all of those places a inline comment explaining this was done for optimization of performance. Because that knowledge gets lost without it.

and [100% optional] what would be super extra if it's possible is to add tests to that specific function that checks that performance in this key area is kept. Even if it's to be manually run with other performance tests, this would be a nice addition to tests you improved in this PR regarding general performance.

Comment on lines +52 to +54
(if (-validator:command-fn m)
true
(malli-error/humanize (-explainer:command-fn m))))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very interesting that moving that to malli/validator has such a performance impact

Comment on lines +82 to +91
(if (= prefix-len 0)
true
(if (< s-len prefix-len)
false
(loop [i 0]
(if (= i prefix-len)
true
(if (= (nth s i) (nth prefix i))
(recur (inc i))
false))))))))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand this is for performance reasons, but can we move this part to separate function with clear name?
Previously this line was very clear when you read it, now you need to analyze it to understand what's this about and why.

^{:doc
"Represents a command found in the instruction map at a specific `path`.
Holds both the path to the command and its command specification data.
Hash is cached at construction time for fast map/set lookups.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

(let [cmd-count (count commands)]
(loop [current-instruction instruction
idx 0
results (transient {})]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's cool about the transient, never had a need to use it.
Great that the code is more efficient now, but the ease to understand this code has gone significantly down :(.

(utils/format-time duration)])))

(def ^:private -coercer:status-map-message
(malli/coercer [:map [:message [:string {:min 5}]]]))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread src/commando/core.cljc
:registry)))

;; -- Execute --
;; -- Full Execute (internal) --
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit pick, a bit verbose, to say full execute 3 times in 3 lines, keep it dry :D

Comment thread src/commando/core.cljc
Comment on lines +183 to +185
:error-data-string - (boolean) serialize exception data as strings
:hook-execute-start - (fn [status-map]) called before execution
:hook-execute-end - (fn [status-map]) called after execution
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Comment on lines 133 to +140
(let [r (debug/execute-trace
#(commando/execute
[builtin/command-fn-spec
builtin/command-from-spec
builtin/command-macro-spec
builtin/command-mutation-spec]
{:value {:commando/mutation :rand-n :v 200}
:result {:commando/macro :sum-n
:v {:commando/from [:value]}}}))]
[builtin/command-fn-spec
builtin/command-from-spec
builtin/command-macro-spec
builtin/command-mutation-spec]
{:value {:commando/mutation :rand-n :v 200}
:result {:commando/macro :sum-n
:v {:commando/from [:value]}}})]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool

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.

2 participants