Skip to content

Resolve the grid time of callable-bond events once per lattice - #2971

Open
igitur wants to merge 1 commit into
lballabio:masterfrom
igitur:callable-bond-grid-time-lookup
Open

igitur wants to merge 1 commit into
lballabio:masterfrom
igitur:callable-bond-grid-time-lookup

Conversation

@igitur

@igitur igitur commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #2962, split out as asked.

DiscretizedCallableFixedRateBond::preAdjustValuesImpl() and postAdjustValuesImpl() run at every step of the rollback over every coupon and every callability, and each test goes through isOnTime(), which runs TimeGrid::index() — a binary search — and then compares. Every event therefore costs a search at every step, for a grid time that cannot change while the method rolls back.

Change

Resolve the grid time of each event once and reuse it. The cache is dropped when the lattice object changes, and also when the same lattice object is rebuilt on a different grid: prices and thresholds are unaffected by a rebuild, but the node values are, so the memoised times cannot be trusted across one.

What it is worth

Instruction counts (callgrind, deterministic) on the benchmark workload: 35,513,641,371 → 33,361,076,814, 6.1% fewer. That comparison is against #2972 alone, so it isolates this change. For scale, master is 61,328,202,039 on the same run, so #2972 plus this is 45.6% below master.

Wall clock on this machine (2 vCPU, medians of three rounds) does not separate it from noise:

Case master this PR alone #2972 alone both
30y monthly calls, a=1e-4 1436 ms 1475 ms 779 ms 859 ms
30y monthly calls, a=0.03 1410 ms 1437 ms 738 ms 781 ms

Run-to-run spread here is ±10–15% at these timings — one round of the combined build came in at 993 ms — so read the wall clock as "no measurable change either way" and the instruction count as the signal. An earlier single round suggested ~10% for this change; with three-round medians that does not survive.

Values are bit-identical on the same 12-configuration harness as #2972.

For discussion

This is the half of #2962 I was least sure of, and the measurement agrees: it buys 6% of instructions at the cost of an invalidation rule, and every step still walks all coupons and callabilities — about 480 events in the 30y monthly case — testing each against the current time. Those times are sorted, so a cursor per event list (advance while time <= t) would remove the scans rather than make them cheap, and would not need the memo bookkeeping at all.

If you would rather have that shape, or would rather drop this and keep only the fitting-parameter change, say which and I will rework it.

@lballabio

Copy link
Copy Markdown
Owner

Thanks. I was also thinking about the T = grid[grid.index(t)]; return close_enough(T, time()) pattern that we're using, cached or not. We seems to be doing this in order to check (via grid.index) that t is, in fact, on the grid. But didn't we adjust the coupon times etc to the grid already? Couldn't we just say close_enough(t, time())?

preAdjustValuesImpl() and postAdjustValuesImpl() test every coupon and
every callability against the current grid time at each step of the
rollback, and each test runs TimeGrid::index() twice (once through
isOnTime()).  The grid does not change while a method rolls back, so the
grid time of an event can be resolved on the first adjustment and reused.

The cache is dropped when the lattice changes, or when the same lattice
object is rebuilt on a different grid: the prices and thresholds are
unaffected by that, but the node values are, so the memoised times cannot
be trusted across a rebuild.

Instruction counts on the benchmark workload (callgrind, deterministic):
35,513,641,371 -> 33,361,076,814, 6.1% fewer.  The wall-clock difference
is inside this machine's run-to-run spread at these timings.  Values are
bit-identical.
@igitur
igitur force-pushed the callable-bond-grid-time-lookup branch from b1968b9 to 4686586 Compare October 29, 2026 16:47
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