Commit 9ae81c1
perf(plan): 21% faster planning, and cars in the random benchmark (#4529)
* perf(kernel): feed the limit arrays straight to array.array (-3% planning time)
double_array was handed [float(limit) for limit in limits] on every simulation. array.array already
coerces each item to a double in C, so both the float() call and the intermediate list were wasted
work: 0.89us -> 0.53us per call, and it stops allocating a throwaway float object per limit.
Measured on the 20-scenario random benchmark: 51.01s -> 49.45s and 49.47s over two runs, with every
scenario byte-identical on metric, cost, cost_pv10, cost_pv90, soc_min, soc_final, battery_cycles
and export_kwh. kernel_parity and model_kernel pass.
Two things deliberately NOT changed, having measured them and found them slower:
- the window fields keep their list comprehensions. map(operator.itemgetter("start"), windows) is
0.88us -> 1.05us at these list lengths, the per-item call overhead outweighing the comprehension.
- predict_soc keeps its indexed loop. dict(zip(range(...), soc_out)) is 13.95us -> 18.69us,
because iterating a ctypes array boxes each double through the sequence protocol.
Both are noted in comments so the next person does not repeat the experiment. line_profiler had
suggested these lines were ~29% and ~13% of the wrapper; its per-line overhead badly inflates
lines executed 73k times, and a microbenchmark tells a different story.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf(plan): memoise the charge selection in the levels pass (-11% planning time)
The price-threshold scan in optimise_charge_limit_price_threads sits four loops deep:
for max_charge_slots / for max_export_slots / for try_charge_freeze / for try_export_freeze
but which charge windows the threshold selects depends only on loop_price, max_charge_slots and
try_charge_freeze - not on either export loop it is nested inside. It was therefore rebuilt
identically once per (max_export_slots, try_export_freeze) pair. Line profiling put it at ~52M
iterations on the heaviest benchmark scenario, the single hottest loop in planning.
Memoised on (max_charge_slots, try_charge_freeze) rather than hoisted, so the iteration order - and
with it every tie-break in the search below - is untouched. Sharing the cached objects between
iterations is safe because neither is mutated after it is built: all_n is copied into pred_item and
charge_mods is only ever read.
The freeze skip also folds into the selection condition, dropping a bare `pass` branch that was
executing 19M times. The cheap freeze test still comes first so the dict lookup is still
short-circuited for the ~37% of iterations that take it.
Measured on the 20-scenario random benchmark: 49.45s -> 44.08s and 43.69s over two runs, every
scenario byte-identical on metric, cost, cost_pv10, cost_pv90, soc_min, soc_final, battery_cycles,
export_kwh and import_kwh_battery.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* perf(plan): stop hit_car_window rounding every slot, and give it a cache
hit_car_window computed dp2(window["kwh"]) for every car charging slot before the intersection test
that discards nearly all of them. For anyone with an EV that made it the most expensive function in
planning: on a car scenario it was 5.04s cumulative of an 11s plan, driving 15.9M dp2 and 16.1M round
calls, while the C++ kernel took 1.4s.
Testing the intersection first and only rounding a slot that actually overlaps is 6.45us -> 1.08us
per call at 48 slots. dp2 is pure, so moving it last cannot change the answer.
It is still asked the same question about the same few windows ~1.2M times per plan, 99.8% of them
from optimise_charge_limit_price_threads, so that caller now keeps a (start, end) -> hit dict for the
length of the pass and hands it in - the same shape as the hit_charge_cache already beside it. The
cache is caller-owned rather than held on self so its lifetime belongs to whoever knows when
car_charging_slots can change.
The random benchmark could not see any of this because it had no cars, so it now generates them:
roughly half the scenarios get 1-2 cars with 4-20 charging slots. The car block is drawn from a
separate RNG stream seeded off the scenario seed, so re-generating reproduces every pre-existing
parameter and data profile bit-identically and old baselines stay comparable - verified. Scenarios
written before this carry no "cars" entry and still run car-less.
apply_scenario_to_predbat now sizes every per-car attribute predbat indexes by car_n, not just the
ones a scenario varies: set_rate_thresholds takes max(car_charging_plan_max_price[:num_cars]) and
raises on an empty slice.
Measured on the 20-scenario benchmark with cars: 53.66s -> 43.50s for the reorder, -> 42.23s with the
cache, every scenario byte-identical on metric, cost, cost_pv10, cost_pv90, soc_min, soc_final,
battery_cycles, export_kwh and import_kwh_battery at each step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(random): rebase the checked-in benchmark baseline onto the car scenarios
random_scenarios.yaml now generates cars for roughly half the scenarios, so the stored baseline -
produced against the car-less set - no longer describes the same workload. Comparing a future run
against it would have shown a large spurious diff on all 11 car scenarios.
Regenerated against the same template the previous baseline used (cases/predbat_debug_agile1.yaml),
with the hit_car_window work in place. 20 scenarios, none failed, all carrying cost_pv10/cost_pv90.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(random): write the results JSON with a trailing newline
json.dump leaves the file without one, so the end-of-file hook rewrote the checked-in baseline every
time it was regenerated - which is what produced the stray pre-commit.ci fixup commit on the earlier
branch. Emit it at the writer so the file lands clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Unit test settings
* Test updates
* Test updates
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>1 parent 338ea68 commit 9ae81c1
8 files changed
Lines changed: 1060 additions & 167 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
406 | 406 | | |
407 | 407 | | |
408 | 408 | | |
| 409 | + | |
409 | 410 | | |
410 | 411 | | |
411 | 412 | | |
| |||
431 | 432 | | |
432 | 433 | | |
433 | 434 | | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
434 | 446 | | |
435 | 447 | | |
436 | 448 | | |
437 | 449 | | |
438 | | - | |
439 | 450 | | |
440 | | - | |
441 | 451 | | |
442 | | - | |
443 | 452 | | |
444 | 453 | | |
445 | | - | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
453 | 468 | | |
454 | 469 | | |
455 | 470 | | |
456 | 471 | | |
457 | 472 | | |
458 | 473 | | |
459 | 474 | | |
460 | | - | |
| 475 | + | |
461 | 476 | | |
462 | 477 | | |
463 | 478 | | |
| |||
3175 | 3190 | | |
3176 | 3191 | | |
3177 | 3192 | | |
| 3193 | + | |
3178 | 3194 | | |
3179 | 3195 | | |
3180 | 3196 | | |
3181 | 3197 | | |
3182 | 3198 | | |
3183 | 3199 | | |
3184 | 3200 | | |
3185 | | - | |
3186 | | - | |
3187 | | - | |
| 3201 | + | |
| 3202 | + | |
| 3203 | + | |
| 3204 | + | |
| 3205 | + | |
| 3206 | + | |
| 3207 | + | |
3188 | 3208 | | |
3189 | 3209 | | |
3190 | 3210 | | |
| |||
3423 | 3443 | | |
3424 | 3444 | | |
3425 | 3445 | | |
| 3446 | + | |
3426 | 3447 | | |
3427 | 3448 | | |
3428 | 3449 | | |
3429 | 3450 | | |
3430 | | - | |
| 3451 | + | |
| 3452 | + | |
3431 | 3453 | | |
| 3454 | + | |
3432 | 3455 | | |
3433 | 3456 | | |
3434 | 3457 | | |
| |||
5141 | 5164 | | |
5142 | 5165 | | |
5143 | 5166 | | |
5144 | | - | |
5145 | | - | |
5146 | | - | |
| 5167 | + | |
| 5168 | + | |
| 5169 | + | |
| 5170 | + | |
| 5171 | + | |
| 5172 | + | |
| 5173 | + | |
| 5174 | + | |
| 5175 | + | |
| 5176 | + | |
| 5177 | + | |
| 5178 | + | |
5147 | 5179 | | |
5148 | | - | |
5149 | | - | |
5150 | | - | |
5151 | | - | |
5152 | | - | |
5153 | | - | |
5154 | | - | |
5155 | | - | |
5156 | | - | |
| 5180 | + | |
| 5181 | + | |
| 5182 | + | |
| 5183 | + | |
| 5184 | + | |
| 5185 | + | |
| 5186 | + | |
| 5187 | + | |
| 5188 | + | |
| 5189 | + | |
| 5190 | + | |
| 5191 | + | |
| 5192 | + | |
| 5193 | + | |
| 5194 | + | |
| 5195 | + | |
| 5196 | + | |
| 5197 | + | |
| 5198 | + | |
| 5199 | + | |
| 5200 | + | |
| 5201 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
282 | 282 | | |
283 | 283 | | |
284 | 284 | | |
285 | | - | |
| 285 | + | |
286 | 286 | | |
287 | 287 | | |
288 | 288 | | |
289 | 289 | | |
290 | 290 | | |
291 | 291 | | |
292 | 292 | | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
293 | 297 | | |
294 | 298 | | |
| 299 | + | |
295 | 300 | | |
296 | 301 | | |
297 | 302 | | |
298 | 303 | | |
299 | 304 | | |
300 | 305 | | |
301 | | - | |
| 306 | + | |
302 | 307 | | |
| 308 | + | |
303 | 309 | | |
304 | 310 | | |
305 | 311 | | |
| |||
507 | 513 | | |
508 | 514 | | |
509 | 515 | | |
510 | | - | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
511 | 521 | | |
512 | 522 | | |
513 | | - | |
| 523 | + | |
514 | 524 | | |
515 | 525 | | |
516 | 526 | | |
| |||
540 | 550 | | |
541 | 551 | | |
542 | 552 | | |
| 553 | + | |
| 554 | + | |
543 | 555 | | |
544 | 556 | | |
545 | 557 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | | - | |
| 143 | + | |
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
153 | | - | |
154 | 149 | | |
155 | | - | |
156 | | - | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
162 | | - | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
163 | 161 | | |
164 | 162 | | |
165 | 163 | | |
| |||
0 commit comments