Problem
With the report phase down to ~1s (#1088, #1090) and the buffer gone (#1101),
capture is what a --coverage run costs. record_line runs per executed line
and does two cache lookups — the tracked decision and the normalized path — and
each one rebuilds its key with
_BASHUNIT_COVERAGE_LOOKUP_KEY_OUT="$1${2//[^a-zA-Z0-9]/_}"
That pattern substitution is the whole cost, and it is not linear in path
length. Measured on Bash 3.2 arm64, 5000 mangles:
| path length |
time |
| 7 chars |
39 ms |
| 59 chars |
653 ms |
| 121 chars |
2746 ms |
So record_line costs 402 µs per line for a real path
(/Users/…/src/coverage/engine.sh), of which 328 µs is the two lookups and
only 29 µs the actual append. A project whose sources sit deeper pays
quadratically more, for nothing it can control.
Proposal
A one-entry memo for the last file seen. Consecutive executed lines come from
the same file almost always — that is what a function body is — so a single
string compare replaces both key rebuilds and both indirect reads:
| 10,000 iterations |
|
| mangle per call |
1393 ms |
| same-file memo |
56 ms |
Expected: ~402 µs to ~75 µs per line, and the path-length sensitivity mostly
gone. The existing per-path caches stay for the miss path.
coverage::init has to clear the memo along with the other in-memory caches,
or a second run in the same shell inherits a stale decision.
Problem
With the report phase down to ~1s (#1088, #1090) and the buffer gone (#1101),
capture is what a
--coveragerun costs.record_lineruns per executed lineand does two cache lookups — the tracked decision and the normalized path — and
each one rebuilds its key with
_BASHUNIT_COVERAGE_LOOKUP_KEY_OUT="$1${2//[^a-zA-Z0-9]/_}"That pattern substitution is the whole cost, and it is not linear in path
length. Measured on Bash 3.2 arm64, 5000 mangles:
So
record_linecosts 402 µs per line for a real path(
/Users/…/src/coverage/engine.sh), of which 328 µs is the two lookups andonly 29 µs the actual append. A project whose sources sit deeper pays
quadratically more, for nothing it can control.
Proposal
A one-entry memo for the last file seen. Consecutive executed lines come from
the same file almost always — that is what a function body is — so a single
string compare replaces both key rebuilds and both indirect reads:
Expected: ~402 µs to ~75 µs per line, and the path-length sensitivity mostly
gone. The existing per-path caches stay for the miss path.
coverage::inithas to clear the memo along with the other in-memory caches,or a second run in the same shell inherits a stale decision.