Skip to content

Give per-user-node vectors inline storage - #1555

Merged
mgovers merged 2 commits into
PowerGridModel:mainfrom
marhofmann:improvement/bus-injection-inline-storage
Aug 27, 2026
Merged

Give per-user-node vectors inline storage#1555
mgovers merged 2 commits into
PowerGridModel:mainfrom
marhofmann:improvement/bus-injection-inline-storage

Conversation

@marhofmann

Copy link
Copy Markdown
Contributor

Each topological node holds one value per user node, and only link-merged supernodes have more than one: in a grid without links every topological node has exactly one. Three of these vectors are rebuilt on every scenario (node_injection, node_flow_from_branch and bus_injection), so each node asks the allocator for room to store a single complex number three times per scenario.

Give them inline room for one element, so the ordinary node does not allocate. Link-merged nodes still work, they just allocate as before. The indexing interface is unchanged. As a side effect the copy from node_injection into bus_injection stops being a heap copy, because both sides are now the same inline type.

Expose the container generically rather than at the point of use: a new common/small_vector.hpp holds a single using declaration for SmallVector, alongside the other type aliases in common/. Other call sites can then reach for it without pulling in boost directly, and the underlying container can be swapped in one place. boost::container::small_vector is header-only and Boost::headers is already an INTERFACE dependency of the power_grid_model target, so this adds nothing to the build. The inline capacity stays a per-use-site decision; here it is 1 because that is exactly what an ordinary topological node holds.

Measured on tests/benchmark_cpp (radial grid, 2605 nodes, 1000-scenario symmetric Newton-Raphson batch), MSVC /O2, against the parent commit, 10 interleaved rounds per arm on an otherwise idle machine, medians:

produce output 2.4487 s -> 2.2470 s 1.09x
math calculation 2.0757 s -> 2.1361 s control, unchanged within noise
whole batch run 4.9217 s -> 4.7514 s 1.04x

The change won 9 of the 10 paired rounds, so the effect is real, but the 0.20 s median gain sits inside main's own 0.69 s run-to-run spread, so treat it as modest rather than decisive.

The gain is smaller than it would have been before #1536, because most of the per-node allocation cost now sits in compute_link_solver, which this does not touch. See the linked issue.

All ten C++ test suites pass, 176999 assertions, built with warnings as errors, including the handling-of-links cases that exercise supernodes holding more than one user node.

Each topological node holds one value per user node, and only link-merged
supernodes have more than one -- in a grid without links every topological
node has exactly one. Three of these vectors are rebuilt on every scenario
(node_injection, node_flow_from_branch and bus_injection), so each node asks
the allocator for room to store a single complex number three times per
scenario.

Give them inline room for one element, so the ordinary node does not
allocate. Link-merged nodes still work, they just allocate as before. The
indexing interface is unchanged. As a side effect the copy from
node_injection into bus_injection stops being a heap copy, because both
sides are now the same inline type.

Expose the container generically rather than at the point of use: a new
common/small_vector.hpp holds a single using declaration for SmallVector,
alongside the other type aliases in common/. Other call sites can then reach
for it without pulling in boost directly, and the underlying container can
be swapped in one place. boost::container::small_vector is header-only and
Boost::headers is already an INTERFACE dependency of the power_grid_model
target, so this adds nothing to the build. The inline capacity stays a
per-use-site decision; here it is 1 because that is exactly what an ordinary
topological node holds.

Measured on tests/benchmark_cpp (radial grid, 2605 nodes, 1000-scenario
symmetric Newton-Raphson batch), MSVC /O2, against the parent commit, 10
interleaved rounds per arm on an otherwise idle machine, medians:

  produce output    2.4487 s -> 2.2470 s   1.09x
  math calculation  2.0757 s -> 2.1361 s   control, unchanged within noise
  whole batch run   4.9217 s -> 4.7514 s   1.04x

The change won 9 of the 10 paired rounds, so the effect is real, but the
0.20 s median gain sits inside main's own 0.69 s run-to-run spread, so treat
it as modest rather than decisive.

The gain is smaller than it would have been before PowerGridModel#1536, because most of
the per-node allocation cost now sits in compute_link_solver, which this
does not touch. See the linked issue.

All ten C++ test suites pass, 176999 assertions, built with warnings as
errors, including the handling-of-links cases that exercise supernodes
holding more than one user node.

Signed-off-by: Martin Hofmann <martin.hofmann-3@ei.thm.de>
@marhofmann

marhofmann commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Linked issue #1554

mgovers
mgovers previously approved these changes Aug 27, 2026

@mgovers mgovers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @marhofmann,

Thanks you once again for your contribution. Everything looks good 👍

Apologies for the change in #1536 that has reduced the benefits of this branch. My educated guess is that that's just repetition of the same/a similar performance regression, in which case applying small vectors there would solve it. I will dive into #1554 now to investigate, but since this PR looks good, let's merge it. It might come in useful for the investigation. Would it be alright if you do another benchmark analysis after our investigation?

@mgovers
mgovers enabled auto-merge August 27, 2026 05:35
Signed-off-by: Martin Hofmann <martin.hofmann-3@ei.thm.de>
auto-merge was automatically disabled August 27, 2026 07:06

Head branch was pushed to by a user without write access

@marhofmann

Copy link
Copy Markdown
Contributor Author

There was a format issue. I fixed it

@mgovers
mgovers enabled auto-merge August 27, 2026 07:13
@TonyXiang8787 TonyXiang8787 added the improvement Improvement on internal implementation label Aug 27, 2026
@TonyXiang8787 TonyXiang8787 linked an issue Aug 27, 2026 that may be closed by this pull request
1 task
@mgovers

mgovers commented Aug 27, 2026

Copy link
Copy Markdown
Member

@TonyXiang8787 the bug report #1554 is separate from this and was a new finding during implementation of this ticket. I am working on a fix for #1554 based off this branch.

@mgovers
mgovers added this pull request to the merge queue Aug 27, 2026
Merged via the queue into PowerGridModel:main with commit eaddbec Aug 27, 2026
32 of 59 checks passed
@marhofmann

Copy link
Copy Markdown
Contributor Author

@mgovers
Now that #1556 exists I could finally measure this properly and the numbers look quite different from what I put in the description. My 1.09× was measured against unfixed main, where the link solver was drowning everything else out.

Produce output, 2605-node grid, 1000-scenario batch, 8 interleaved rounds:

build Produce output vs pre-#1536
pre-#1536 0.222 s
main 1.971 s 8.9×
main + #1556 0.421 s 1.90×
main + #1556 + this PR 0.249 s 1.12×

So on top of #1556 this is worth 1.69×, and together they get ~98.5% of the regression back. Whole run 3.93 s → 2.19 s.
The ~0.03 s left is probably the branch-flow accumulation #1536 added, which is needed work. So I doubt there's much more to win on the input side or in the link solver.

@mgovers

mgovers commented Aug 27, 2026

Copy link
Copy Markdown
Member

Perfect news and once again thank you for the extensive analysis!

@mgovers

mgovers commented Aug 27, 2026

Copy link
Copy Markdown
Member

I wonder how many other locations can benefit significally from the small vector. Topology reduction is a very likely candidate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improvement on internal implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants